From cba73ff5b31ec3aeaa2617128488f5e0d306f8e5 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Mon, 19 Sep 2022 11:15:50 +0800 Subject: [PATCH 01/18] test: Add test for method mixins. --- test/scip/testdata/mixin.rb | 40 +++++++++++++++ test/scip/testdata/mixin.snapshot.rb | 74 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 test/scip/testdata/mixin.rb create mode 100644 test/scip/testdata/mixin.snapshot.rb diff --git a/test/scip/testdata/mixin.rb b/test/scip/testdata/mixin.rb new file mode 100644 index 000000000..7694000e8 --- /dev/null +++ b/test/scip/testdata/mixin.rb @@ -0,0 +1,40 @@ +# typed: true + +module M + def f + puts 'M.f' + end +end + +class C1 + include M + def f + puts 'C1.f' + end +end + +# f refers to C1.f +class C2 < C1 +end + +# f refers to C1.f +class C3 < C1 + include M +end + +class D1 + def f + puts 'D1.f' + end +end + +class D2 + include M +end + +C1.new.f # C1.f +C2.new.f # C1.f +C3.new.f # C1.f + +D1.new.f # D1.f +D2.new.f # M.f diff --git a/test/scip/testdata/mixin.snapshot.rb b/test/scip/testdata/mixin.snapshot.rb new file mode 100644 index 000000000..3da652537 --- /dev/null +++ b/test/scip/testdata/mixin.snapshot.rb @@ -0,0 +1,74 @@ + # typed: true + + module M +# ^ definition [..] M# + def f +# ^ definition [..] M#f(). + puts 'M.f' + end + end + + class C1 +# ^^ definition [..] C1# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] M# + def f +# ^ definition [..] C1#f(). + puts 'C1.f' +# ^^^^ reference [..] Kernel#puts(). + end + end + + # f refers to C1.f + class C2 < C1 +# ^^ definition [..] C2# +# ^^ definition [..] C1# + end + + # f refers to C1.f + class C3 < C1 +# ^^ definition [..] C3# +# ^^ definition [..] C1# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] M# + end + + class D1 +# ^^ definition [..] D1# + def f +# ^ definition [..] D1#f(). + puts 'D1.f' +# ^^^^ reference [..] Kernel#puts(). + end + end + + class D2 +# ^^ definition [..] D2# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] M# + end + + C1.new.f # C1.f +#^^ reference [..] C1# +# ^^^ reference [..] Class#new(). +# ^ reference [..] C1#f(). + C2.new.f # C1.f +#^^ reference [..] C2# +# ^^^ reference [..] Class#new(). +# ^ reference [..] C1#f(). + C3.new.f # C1.f +#^^ reference [..] C3# +# ^^^ reference [..] Class#new(). +# ^ reference [..] C1#f(). + + D1.new.f # D1.f +#^^ reference [..] D1# +# ^^^ reference [..] Class#new(). +# ^ reference [..] D1#f(). + D2.new.f # M.f +#^^ reference [..] D2# +# ^^^ reference [..] Class#new(). +# ^ reference [..] M#f(). From 2d4cdae62fa2ebdb6d9b72d2933334eee33b091f Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Tue, 20 Sep 2022 20:47:27 +0800 Subject: [PATCH 02/18] fix: Add support for fields in mixins. --- scip_indexer/BUILD | 2 + scip_indexer/SCIPFieldResolve.cc | 140 +++ scip_indexer/SCIPFieldResolve.h | 122 +++ scip_indexer/SCIPIndexer.cc | 216 +++-- scip_indexer/SCIPSymbolRef.cc | 63 +- scip_indexer/SCIPSymbolRef.h | 20 +- .../testdata/field_inheritance.snapshot.rb | 41 +- .../testdata/fields_and_attrs.snapshot.rb | 6 +- test/scip/testdata/hoverdocs.snapshot.rb | 23 +- test/scip/testdata/inheritance.snapshot.rb | 9 +- test/scip/testdata/mixin.rb | 452 ++++++++- test/scip/testdata/mixin.snapshot.rb | 865 +++++++++++++++++- test/scip/testdata/type_change.snapshot.rb | 49 +- test/scip_test_runner.cc | 43 +- 14 files changed, 1895 insertions(+), 156 deletions(-) create mode 100644 scip_indexer/SCIPFieldResolve.cc create mode 100644 scip_indexer/SCIPFieldResolve.h diff --git a/scip_indexer/BUILD b/scip_indexer/BUILD index b4499aabd..d8b251681 100644 --- a/scip_indexer/BUILD +++ b/scip_indexer/BUILD @@ -30,6 +30,8 @@ cc_library( srcs = [ "Debug.cc", "Debug.h", + "SCIPFieldResolve.cc", + "SCIPFieldResolve.h", "SCIPIndexer.cc", "SCIPProtoExt.cc", "SCIPProtoExt.h", diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc new file mode 100644 index 000000000..d6a4cf1df --- /dev/null +++ b/scip_indexer/SCIPFieldResolve.cc @@ -0,0 +1,140 @@ +#include + +#include "absl/strings/ascii.h" + +#include "common/common.h" +#include "core/GlobalState.h" +#include "core/SymbolRef.h" +#include "core/Symbols.h" + +#include "scip_indexer/Debug.h" +#include "scip_indexer/SCIPFieldResolve.h" + +using namespace std; + +namespace sorbet::scip_indexer { + +string FieldQueryResult::Data::showRaw(const core::GlobalState &gs) const { + switch (this->kind()) { + case Kind::FromDeclared: + return this->originalSymbol().showRaw(gs); + case Kind::FromUndeclared: + return fmt::format("In({})", absl::StripAsciiWhitespace(this->originalClass().showFullName(gs))); + } +} + +string FieldQueryResult::showRaw(const core::GlobalState &gs) const { + if (this->mixedIn->empty()) { + return fmt::format("FieldQueryResult(inherited: {})", this->inherited.showRaw(gs)); + } + return fmt::format("FieldQueryResult(inherited: {}, mixins: {})", this->inherited.showRaw(gs), + showVec(*this->mixedIn.get(), [&gs](const auto &mixin) -> string { return mixin.showRaw(gs); })); +} + +void FieldResolver::resetMixins() { + this->mixinQueue.clear(); +} + +// Compute all transitively included modules which mention the field being queried. +// +// If an include chain for a field looks like class C.@f <- module M2.@f <- module M1.@f, +// both M1 and M2 will be included in the results (this avoids any kind of postprocessing +// of a transitive closure of relationships at the cost of a larger index). +void FieldResolver::findUnresolvedFieldInMixinsTransitive(const core::GlobalState &gs, FieldQuery query, + vector &out) { + this->mixinQueue.clear(); + for (auto mixin : query.start.data(gs)->mixins()) { + this->mixinQueue.push_back(mixin); + } + auto field = query.field; + using Data = FieldQueryResult::Data; + while (auto m = this->mixinQueue.try_pop_front()) { + auto mixin = m.value(); + auto sym = mixin.data(gs)->findMember(gs, field); + if (sym.exists()) { + out.push_back(Data(sym)); + continue; + } + auto it = gs.unresolvedFields.find(mixin); + if (it != gs.unresolvedFields.end() && it->second.contains(field)) { + out.push_back(Data(mixin)); + } + } +} + +FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, + FieldQuery query) { + auto start = query.start; + auto field = query.field; + + auto fieldText = query.field.shortName(gs); + auto isInstanceVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] != '@'; + auto isClassInstanceVar = isInstanceVar && start.data(gs)->isSingletonClass(gs); + // Class instance variables are not inherited, unlike ordinary instance + // variables or class variables. + if (isClassInstanceVar) { + return FieldQueryResult::Data(start); + } + auto isClassVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] == '@'; + if (isClassVar && !start.data(gs)->isSingletonClass(gs)) { + // Triggered when undeclared class variables are accessed from instance methods. + start = start.data(gs)->lookupSingletonClass(gs); + } + + if (gs.unresolvedFields.find(start) == gs.unresolvedFields.end() || + !gs.unresolvedFields.find(start)->second.contains(field)) { + // Triggered by code patterns like: + // # top-level + // def MyClass.method + // # blah + // end + // which is not supported by Sorbet. + LOG_DEBUG(gs, loc, + fmt::format("couldn't find field {} in class {};\n" + "are you using a code pattern like def MyClass.method which is unsupported by Sorbet?", + field.exists() ? field.toString(gs) : "", + start.exists() ? start.showFullName(gs) : "")); + // As a best-effort guess, assume that the definition is + // in this class but we somehow missed it. + return FieldQueryResult::Data(start); + } + + auto best = start; + auto cur = start; + while (cur.exists()) { + auto klass = cur.data(gs); + auto sym = klass->findMember(gs, field); + if (sym.exists()) { // TODO(varun): Is this early exit justified? + // Maybe it is possible to hit this in multiple ancestors? + return FieldQueryResult::Data(sym); + } + auto it = gs.unresolvedFields.find(cur); + if (it != gs.unresolvedFields.end() && it->second.contains(field)) { + best = cur; + } + + if (cur == klass->superClass()) { // FIXME(varun): Handle mix-ins + break; + } + cur = klass->superClass(); + } + return FieldQueryResult::Data(best); +} + +pair FieldResolver::findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, + FieldQuery query) { + auto cacheIt = this->cache.find(query); + if (cacheIt != this->cache.end()) { + return {cacheIt->second, true}; + } + auto inherited = this->findUnresolvedFieldInInheritanceChain(gs, loc, query); + using Data = FieldQueryResult::Data; + vector mixins; + findUnresolvedFieldInMixinsTransitive(gs, query, mixins); + auto [it, inserted] = + this->cache.insert({query, FieldQueryResult{inherited, make_shared>(move(mixins))}}); + ENFORCE(inserted); + return {it->second, false}; +} + +} // namespace sorbet::scip_indexer \ No newline at end of file diff --git a/scip_indexer/SCIPFieldResolve.h b/scip_indexer/SCIPFieldResolve.h new file mode 100644 index 000000000..11f3ce121 --- /dev/null +++ b/scip_indexer/SCIPFieldResolve.h @@ -0,0 +1,122 @@ + +#ifndef SORBET_SCIP_FIELD_RESOLVE +#define SORBET_SCIP_FIELD_RESOLVE + +#include +#include +#include + +#include "core/NameRef.h" +#include "core/SymbolRef.h" + +namespace sorbet::scip_indexer { + +struct FieldQuery final { + sorbet::core::ClassOrModuleRef start; + sorbet::core::NameRef field; + + bool operator==(const FieldQuery &other) const noexcept { + return this->start == other.start && this->field == other.field; + } +}; + +template H AbslHashValue(H h, const FieldQuery &q) { + return H::combine(std::move(h), q.start, q.field); +} + +struct FieldQueryResult final { + enum class Kind : bool { + FromDeclared, + FromUndeclared, + }; + + class Data { + union Storage { + core::ClassOrModuleRef owner; + core::SymbolRef symbol; + Storage() { + memset(this, 0, sizeof(Storage)); + } + } storage; + Kind _kind; + + public: + Data(Data &&) = default; + Data(const Data &) = default; + Kind kind() const { + return this->_kind; + } + core::ClassOrModuleRef originalClass() const { + ENFORCE(this->kind() == Kind::FromUndeclared); + return this->storage.owner; + } + core::SymbolRef originalSymbol() const { + ENFORCE(this->kind() == Kind::FromUndeclared); + return this->storage.symbol; + } + Data(core::ClassOrModuleRef klass) : _kind(Kind::FromUndeclared) { + this->storage.owner = klass; + } + Data(core::SymbolRef sym) : _kind(Kind::FromDeclared) { + this->storage.symbol = sym; + } + + std::string showRaw(const core::GlobalState &) const; + }; + + Data inherited; + std::shared_ptr> mixedIn; + + std::string showRaw(const core::GlobalState &gs) const; +}; + +// Non-shrinking queue for cheap-to-copy types. +template class BasicQueue final { + std::vector storage; + size_t current; + +public: + BasicQueue() = default; + BasicQueue(BasicQueue &&) = default; + BasicQueue &operator=(BasicQueue &&) = default; + BasicQueue(const BasicQueue &) = delete; + BasicQueue &operator=(const BasicQueue &) = delete; + + void clear() { + this->storage.clear(); + this->current = 0; + } + void push_back(T val) { + this->storage.push_back(val); + } + std::optional try_pop_front() { + if (this->current >= this->storage.size()) { + return {}; + } + auto ret = this->storage[this->current]; + this->current++; + return ret; + } +}; + +class FieldResolver final { + sorbet::UnorderedMap cache; + BasicQueue mixinQueue; + +public: + std::pair findUnresolvedFieldTransitive(const core::GlobalState &gs, + core::Loc loc, FieldQuery query); + +private: + void resetMixins(); + + void findUnresolvedFieldInMixinsTransitive(const sorbet::core::GlobalState &gs, FieldQuery query, + std::vector &out); + + FieldQueryResult::Data findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, + FieldQuery query); +}; + +} // namespace sorbet::scip_indexer + +#endif // SORBET_SCIP_FIELD_RESOLVE \ No newline at end of file diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 13cd9a0cd..10f68c157 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -33,6 +33,7 @@ #include "sorbet_version/sorbet_version.h" #include "scip_indexer/Debug.h" +#include "scip_indexer/SCIPFieldResolve.h" #include "scip_indexer/SCIPProtoExt.h" #include "scip_indexer/SCIPSymbolRef.h" #include "scip_indexer/SCIPUtils.h" @@ -134,6 +135,10 @@ enum class Emitted { /// Per-thread state storing information to be emitting in a SCIP index. /// /// The states are implicitly merged at the time of emitting the index. +/// +/// WARNING: A SCIPState value will in general be reused across files, +/// so caches should generally directly or indirectly include a FileRef +/// as part of key (e.g. via core::Loc). class SCIPState { string symbolScratchBuffer; UnorderedMap symbolStringCache; @@ -153,6 +158,15 @@ class SCIPState { /// This is mainly present to avoid emitting duplicate occurrences /// for DSL-like constructs like prop/def_delegator. UnorderedSet> symbolOccurrenceCache; + + /// Map to keep track of symbols which lack a direct definition, + /// and are indirectly defined by another symbol through a Relationship. + /// + /// Ideally, 'UntypedGenericSymbolRef' would not be duplicated across files + /// but we keep these separate per file to avoid weirdness where logically + /// different but identically named classes exist in different files. + UnorderedMap> potentialRefOnlySymbols; + // ^ Naively, I would think that that shouldn't happen because we don't traverse // rewriter-synthesized method bodies, but it does seem to happen. // @@ -174,6 +188,15 @@ class SCIPState { UnorderedSet> emittedSymbols; UnorderedMap> symbolMap; + /// Stores the relationships that apply to a field or a method. + /// + /// Ideally, 'UntypedGenericSymbolRef' would not be duplicated across files + /// but we keep these separate per file to avoid weirdness where logically + /// different but identically named classes exist in different files. + UnorderedMap relationshipsMap; + + FieldResolver fieldResolver; + vector documents; vector externalSymbols; @@ -223,8 +246,13 @@ class SCIPState { } private: - Emitted saveSymbolInfo(core::FileRef file, const string &symbolString, const SmallVec &docs) { - if (this->emittedSymbols.contains({file, symbolString})) { + bool alreadyEmittedSymbolInfo(core::FileRef file, const string &symbolString) { + return this->emittedSymbols.contains({file, symbolString}); + } + + Emitted saveSymbolInfo(core::FileRef file, const string &symbolString, const SmallVec &docs, + const SmallVec &rels) { + if (this->alreadyEmittedSymbolInfo(file, symbolString)) { return Emitted::Earlier; } scip::SymbolInformation symbolInfo; @@ -232,15 +260,19 @@ class SCIPState { for (auto &doc : docs) { symbolInfo.add_documentation(doc); } + for (auto &rel : rels) { + *symbolInfo.add_relationships() = rel; + } this->symbolMap[file].push_back(symbolInfo); return Emitted::Now; } absl::Status saveDefinitionImpl(const core::GlobalState &gs, core::FileRef file, const string &symbolString, - core::Loc occLoc, const SmallVec &docs) { + core::Loc occLoc, const SmallVec &docs, + const SmallVec &rels) { ENFORCE(!symbolString.empty()); - auto emitted = this->saveSymbolInfo(file, symbolString, docs); + auto emitted = this->saveSymbolInfo(file, symbolString, docs, rels); occLoc = trimColonColonPrefix(gs, occLoc); scip::Occurrence occurrence; @@ -325,6 +357,16 @@ class SCIPState { return !inserted; } + void saveRelationships(const core::GlobalState &gs, core::FileRef file, UntypedGenericSymbolRef untypedSymRef, + SmallVec &rels) { + untypedSymRef.saveRelationships(gs, this->relationshipsMap[file], rels, + [this, &gs](UntypedGenericSymbolRef sym, std::string &out) { + auto status = this->saveSymbolString(gs, sym, nullptr); + ENFORCE(status.ok()); + out = *status.value(); + }); + } + public: absl::Status saveDefinition(const core::GlobalState &gs, core::FileRef file, OwnedLocal occ, core::TypePtr type) { if (this->cacheOccurrence(gs, file, occ, scip::SymbolRole::Definition)) { @@ -337,7 +379,7 @@ class SCIPState { ENFORCE(var.has_value(), "Failed to find source text for definition of local variable"); docStrings.push_back(fmt::format("```ruby\n{} ({})\n```", var.value(), type.show(gs))); } - return this->saveDefinitionImpl(gs, file, occ.toSCIPString(gs, file), loc, docStrings); + return this->saveDefinitionImpl(gs, file, occ.toSCIPString(gs, file), loc, docStrings, {}); } // Save definition when you have a sorbet Symbol. @@ -350,11 +392,12 @@ class SCIPState { auto occLoc = loc.has_value() ? core::Loc(file, loc.value()) : symRef.symbolLoc(gs); scip::Symbol symbol; - auto status = symRef.withoutType().symbolForExpr(gs, this->gemMetadata, occLoc, symbol); + auto untypedSymRef = symRef.withoutType(); + auto status = untypedSymRef.symbolForExpr(gs, this->gemMetadata, occLoc, symbol); if (!status.ok()) { return status; } - absl::StatusOr valueOrStatus(this->saveSymbolString(gs, symRef.withoutType(), &symbol)); + absl::StatusOr valueOrStatus(this->saveSymbolString(gs, untypedSymRef, &symbol)); if (!valueOrStatus.ok()) { return valueOrStatus.status(); } @@ -362,7 +405,11 @@ class SCIPState { SmallVec docs; symRef.saveDocStrings(gs, symRef.definitionType(), occLoc, docs); - return this->saveDefinitionImpl(gs, file, symbolString, occLoc, docs); + + SmallVec rels; + this->saveRelationships(gs, file, symRef.withoutType(), rels); + + return this->saveDefinitionImpl(gs, file, symbolString, occLoc, docs, rels); } absl::Status saveReference(const core::GlobalState &gs, core::FileRef file, OwnedLocal occ, @@ -415,10 +462,43 @@ class SCIPState { symRef.saveDocStrings(gs, overrideType.value(), loc, overrideDocs); } } + + // If we haven't emitted a SymbolInfo yet, record that we may want to emit + // a SymbolInfo in the future, if it isn't emitted later in this file. + if (!this->emittedSymbols.contains({file, symbolString})) { + auto &rels = this->relationshipsMap[file]; + if (rels.contains(symRef.withoutType())) { + this->potentialRefOnlySymbols[file].insert(symRef.withoutType()); + } + } + this->saveReferenceImpl(gs, file, symbolString, overrideDocs, occLoc, symbol_roles); return absl::OkStatus(); } + void finalizeRefOnlySymbolInfos(const core::GlobalState &gs, core::FileRef file) { + auto &potentialSyms = this->potentialRefOnlySymbols[file]; + fmt::print(stderr, "potentialSyms = {}\n", + showSet(potentialSyms, [&](UntypedGenericSymbolRef sym) -> string { return sym.showRaw(gs); })); + fmt::print(stderr, "relMap = {}\n", showRawRelationshipsMap(gs, this->relationshipsMap[file])); + + for (auto symRef : potentialSyms) { + auto valueOrError = this->saveSymbolString(gs, symRef, nullptr); + if (!valueOrError.ok()) { + continue; + } + auto &symbolString = *valueOrError.value(); + // Avoid calling saveRelationships if we already emitted this. + // saveSymbolInfo does this check too, so it isn't strictly needed. + if (this->alreadyEmittedSymbolInfo(file, symbolString)) { + continue; + } + SmallVec rels; + this->saveRelationships(gs, file, symRef, rels); + this->saveSymbolInfo(file, symbolString, {}, rels); + } + } + void saveDocument(const core::GlobalState &gs, const core::FileRef file) { scip::Document document; // TODO(varun): Double-check the path code and maybe document it, @@ -463,63 +543,6 @@ string format_ancestry(const core::GlobalState &gs, core::SymbolRef sym) { return out.str(); } -static absl::variant -findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, core::ClassOrModuleRef start, - core::NameRef field) { - auto fieldText = field.shortName(gs); - auto isInstanceVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] != '@'; - auto isClassInstanceVar = isInstanceVar && start.data(gs)->isSingletonClass(gs); - // Class instance variables are not inherited, unlike ordinary instance - // variables or class variables. - if (isClassInstanceVar) { - return start; - } - auto isClassVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] == '@'; - if (isClassVar && !start.data(gs)->isSingletonClass(gs)) { - // Triggered when undeclared class variables are accessed from instance methods. - start = start.data(gs)->lookupSingletonClass(gs); - } - - // TODO(varun): Should we add a cache here? It seems wasteful to redo - // work for every occurrence. - if (gs.unresolvedFields.find(start) == gs.unresolvedFields.end() || - !gs.unresolvedFields.find(start)->second.contains(field)) { - // Triggered by code patterns like: - // # top-level - // def MyClass.method - // # blah - // end - // which is not supported by Sorbet. - LOG_DEBUG(gs, loc, - fmt::format("couldn't find field {} in class {};\n" - "are you using a code pattern like def MyClass.method which is unsupported by Sorbet?", - field.exists() ? field.toString(gs) : "", - start.exists() ? start.showFullName(gs) : "")); - // As a best-effort guess, assume that the definition is - // in this class but we somehow missed it. - return start; - } - - auto best = start; - auto cur = start; - while (cur.exists()) { - auto klass = cur.data(gs); - auto sym = klass->findMember(gs, field); - if (sym.exists()) { - return sym; - } - auto it = gs.unresolvedFields.find(cur); - if (it != gs.unresolvedFields.end() && it->second.contains(field)) { - best = cur; - } - if (cur == klass->superClass()) { // FIXME(varun): Handle mix-ins - break; - } - cur = klass->superClass(); - } - return best; -} - // Loosely inspired by AliasesAndKeywords in IREmitterContext.cc class AliasMap final { public: @@ -534,7 +557,8 @@ class AliasMap final { public: AliasMap() = default; - void populate(const core::Context &ctx, const cfg::CFG &cfg) { + void populate(const core::Context &ctx, const cfg::CFG &cfg, FieldResolver &fieldResolver, + RelationshipsMap &relMap) { this->map = {}; auto &gs = ctx.state; auto method = ctx.owner; @@ -567,25 +591,42 @@ class AliasMap final { {GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc, false}}); continue; } - auto result = findUnresolvedFieldTransitive(ctx, ctx.locAt(bind.loc), klass.asClassOrModuleRef(), - instr->name); - if (absl::holds_alternative(result)) { - auto klass = absl::get(result); - if (klass.exists()) { - this->map.insert( // no trim(...) because undeclared fields shouldn't have :: - {bind.bind.variable, - {GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc, - false}}); + auto [result, cacheHit] = fieldResolver.findUnresolvedFieldTransitive( + ctx, ctx.locAt(bind.loc), {klass.asClassOrModuleRef(), instr->name}); + auto checkExists = [&](bool exists, const std::string &text) { + ENFORCE(exists, + "Returned non-existent {} from findUnresolvedFieldTransitive with start={}, " + "field={}, file={}, loc={}", + text, klass.exists() ? klass.toStringFullName(gs) : "", + instr->name.exists() ? instr->name.toString(gs) : "", + ctx.file.data(gs).path(), ctx.locAt(bind.loc).showRawLineColumn(gs)); + }; + switch (result.inherited.kind()) { + case FieldQueryResult::Kind::FromUndeclared: { + checkExists(result.inherited.originalClass().exists(), "class"); + auto namedSymRef = GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type); + if (!cacheHit) { + // It may be the case that the mixin values are already stored because of the + // traversal in some other function. In that case, don't bother overriding. + relMap.insert({namedSymRef.withoutType(), result}); + } + // no trim(...) because undeclared fields shouldn't have :: + this->map.insert({bind.bind.variable, {namedSymRef, bind.loc, false}}); + break; } - } else if (absl::holds_alternative(result)) { - auto fieldSym = absl::get(result); - if (fieldSym.exists()) { - this->map.insert( - {bind.bind.variable, - {GenericSymbolRef::declaredField(fieldSym, bind.bind.type), trim(bind.loc), false}}); + case FieldQueryResult::Kind::FromDeclared: { + auto fieldSym = result.inherited.originalSymbol(); + checkExists(fieldSym.exists(), "field"); + auto namedSymRef = + fieldSym.owner(gs) == klass + ? GenericSymbolRef::declaredField(fieldSym, bind.bind.type) + : GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type); + if (!cacheHit) { + relMap.insert({namedSymRef.withoutType(), result}); + } + this->map.insert({bind.bind.variable, {namedSymRef, trim(bind.loc), false}}); + break; } - } else { - ENFORCE(false, "Should've handled all cases of variant earlier"); } continue; } @@ -831,7 +872,10 @@ class CFGTraversal final { public: void traverse(const cfg::CFG &cfg) { - this->aliasMap.populate(this->ctx, cfg); + this->aliasMap.populate(this->ctx, cfg, this->scipState.fieldResolver, + this->scipState.relationshipsMap[ctx.file]); + fmt::print(stderr, "log: [traverse] relationshipsMap = {}\n", + showRawRelationshipsMap(this->ctx.state, this->scipState.relationshipsMap[ctx.file])); auto &gs = this->ctx.state; auto file = this->ctx.file; auto method = this->ctx.owner; @@ -1100,7 +1144,9 @@ class SCIPSemanticExtension : public SemanticExtension { if (this->doNothing()) { return; } - getSCIPState()->saveDocument(gs, file); + auto scipState = this->getSCIPState(); + scipState->finalizeRefOnlySymbolInfos(gs, file); + scipState->saveDocument(gs, file); }; virtual void finishTypecheck(const core::GlobalState &gs) const override { if (this->doNothing()) { diff --git a/scip_indexer/SCIPSymbolRef.cc b/scip_indexer/SCIPSymbolRef.cc index 4d1db0117..39f947287 100644 --- a/scip_indexer/SCIPSymbolRef.cc +++ b/scip_indexer/SCIPSymbolRef.cc @@ -6,17 +6,27 @@ #include #include "absl/status/status.h" +#include "absl/strings/ascii.h" #include "absl/strings/str_replace.h" +#include "spdlog/fmt/fmt.h" #include "core/Loc.h" #include "main/lsp/lsp.h" +#include "scip_indexer/Debug.h" #include "scip_indexer/SCIPSymbolRef.h" using namespace std; namespace sorbet::scip_indexer { +string showRawRelationshipsMap(const core::GlobalState &gs, const RelationshipsMap &relMap) { + return showMap(relMap, [&gs](const UntypedGenericSymbolRef &ugsr, const auto &result) -> string { + return fmt::format("{}: (inherited={}, mixins={})", ugsr.showRaw(gs), result.inherited.showRaw(gs), + showVec(*result.mixedIn, [&gs](const auto &mixin) -> string { return mixin.showRaw(gs); })); + }); +} + // Try to compute a scip::Symbol for this value. absl::Status UntypedGenericSymbolRef::symbolForExpr(const core::GlobalState &gs, const GemMetadata &metadata, optional loc, scip::Symbol &symbol) const { @@ -83,9 +93,56 @@ absl::Status UntypedGenericSymbolRef::symbolForExpr(const core::GlobalState &gs, string UntypedGenericSymbolRef::showRaw(const core::GlobalState &gs) const { if (this->name.exists()) { - return fmt::format("UGSR(owner: {}, name: {})", this->selfOrOwner.showFullName(gs), this->name.toString(gs)); + return fmt::format("UGSR({}.{})", absl::StripAsciiWhitespace(this->selfOrOwner.showFullName(gs)), + this->name.toString(gs)); + } + return fmt::format("UGSR({})", absl::StripAsciiWhitespace(this->selfOrOwner.showFullName(gs))); +} + +void UntypedGenericSymbolRef::saveRelationships( + const core::GlobalState &gs, const RelationshipsMap &relationshipMap, SmallVec &rels, + const absl::FunctionRef &saveSymbolString) const { + auto it = relationshipMap.find(*this); + if (it == relationshipMap.end()) { + return; + } + using Kind = FieldQueryResult::Kind; + auto saveSymbol = [&](FieldQueryResult::Data data, scip::Relationship &rel) { + switch (data.kind()) { + case Kind::FromUndeclared: + saveSymbolString(UntypedGenericSymbolRef::undeclared(data.originalClass(), this->name), + *rel.mutable_symbol()); + break; + case Kind::FromDeclared: + saveSymbolString(UntypedGenericSymbolRef::declared(data.originalSymbol()), *rel.mutable_symbol()); + break; + } + ENFORCE(!rel.symbol().empty()); + rels.push_back(move(rel)); + }; + + auto result = it->second; + + bool saveInherited = false; + switch (result.inherited.kind()) { + case Kind::FromUndeclared: + saveInherited = core::SymbolRef(result.inherited.originalClass()) != this->selfOrOwner; + break; + case Kind::FromDeclared: + saveInherited = result.inherited.originalSymbol().owner(gs) != this->selfOrOwner; + } + + if (saveInherited) { + scip::Relationship rel; + rel.set_is_definition(true); + saveSymbol(result.inherited, rel); + } + + for (auto mixin : *result.mixedIn) { + scip::Relationship rel; + rel.set_is_reference(true); + saveSymbol(mixin, rel); } - return fmt::format("UGSR(symbol: {})", this->selfOrOwner.showFullName(gs)); } string GenericSymbolRef::showRaw(const core::GlobalState &gs) const { @@ -199,4 +256,4 @@ core::Loc GenericSymbolRef::symbolLoc(const core::GlobalState &gs) const { } } -} // namespace sorbet::scip_indexer \ No newline at end of file +} // namespace sorbet::scip_indexer diff --git a/scip_indexer/SCIPSymbolRef.h b/scip_indexer/SCIPSymbolRef.h index 4dfd6fc01..af5ad3efe 100644 --- a/scip_indexer/SCIPSymbolRef.h +++ b/scip_indexer/SCIPSymbolRef.h @@ -15,9 +15,12 @@ #include "core/Symbols.h" #include "core/TypePtr.h" +#include "scip_indexer/SCIPFieldResolve.h" + namespace scip { // Avoid needlessly including protobuf header here. class Symbol; -} +class Relationship; +} // namespace scip namespace sorbet::scip_indexer { @@ -49,6 +52,12 @@ class GemMetadata final { } }; +class UntypedGenericSymbolRef; + +using RelationshipsMap = UnorderedMap; + +std::string showRawRelationshipsMap(const core::GlobalState &gs, const RelationshipsMap &relMap); + // Simplified version of GenericSymbolRef that doesn't care about types. // // Primarily for use in storing/looking up information in maps/sets, @@ -84,6 +93,11 @@ class UntypedGenericSymbolRef final { absl::Status symbolForExpr(const core::GlobalState &gs, const GemMetadata &metadata, std::optional loc, scip::Symbol &symbol) const; + void + saveRelationships(const core::GlobalState &gs, const RelationshipsMap &relationshipMap, + SmallVec &rels, + const absl::FunctionRef &saveSymbolString) const; + std::string showRaw(const core::GlobalState &gs) const; }; @@ -210,8 +224,10 @@ class GenericSymbolRef final { return this->selfOrOwner; } +private: static bool isSorbetInternal(const core::GlobalState &gs, core::SymbolRef sym); +public: bool isSorbetInternalClassOrMethod(const core::GlobalState &gs) const { switch (this->kind()) { case Kind::UndeclaredField: @@ -231,4 +247,4 @@ class GenericSymbolRef final { } // namespace sorbet::scip_indexer -#endif // SORBET_SCIP_SYMBOL_REF \ No newline at end of file +#endif // SORBET_SCIP_SYMBOL_REF diff --git a/test/scip/testdata/field_inheritance.snapshot.rb b/test/scip/testdata/field_inheritance.snapshot.rb index 8e5959812..89cf1514d 100644 --- a/test/scip/testdata/field_inheritance.snapshot.rb +++ b/test/scip/testdata/field_inheritance.snapshot.rb @@ -26,14 +26,17 @@ class C2 < C1 def get_inherited_ivar # ^^^^^^^^^^^^^^^^^^ definition [..] C2#get_inherited_ivar(). return @f + @h -# ^^ reference [..] C1#`@f`. -# ^^ reference [..] C1#`@h`. +# ^^ reference [..] C2#`@f`. +# relation definition=[..] C1#`@f`. +# ^^ reference [..] C2#`@h`. +# relation definition=[..] C1#`@h`. end def set_inherited_ivar # ^^^^^^^^^^^^^^^^^^ definition [..] C2#set_inherited_ivar(). @f = 10 -# ^^ definition [..] C1#`@f`. +# ^^ definition [..] C2#`@f`. +# relation definition=[..] C1#`@f`. return end @@ -57,9 +60,11 @@ class C3 < C2 def refs # ^^^^ definition [..] C3#refs(). @f = @g + @i -# ^^ definition [..] C1#`@f`. -# ^^ reference [..] C2#`@g`. -# ^^ reference [..] C1#`@i`. +# ^^ definition [..] C3#`@f`. +# relation definition=[..] C1#`@f`. +# ^^ reference [..] C3#`@g`. +# relation definition=[..] C2#`@g`. +# ^^ reference [..] C3#`@i`. return end end @@ -148,10 +153,13 @@ def self.get # ^^^ definition [..] ``#get(). @@d2_x = @@d1_v + @@d1_x # ^^^^^^ definition [..] ``#`@@d2_x`. -# ^^^^^^ reference [..] ``#`@@d1_v`. -# ^^^^^^ reference [..] ``#`@@d1_x`. +# ^^^^^^ reference [..] ``#`@@d1_v`. +# relation definition=[..] ``#`@@d1_v`. +# ^^^^^^ reference [..] ``#`@@d1_x`. +# relation definition=[..] ``#`@@d1_x`. @@d1_y + @@d1_z -# ^^^^^^ reference [..] ``#`@@d1_y`. +# ^^^^^^ reference [..] ``#`@@d1_y`. +# relation definition=[..] ``#`@@d1_y`. # ^^^^^^ reference [..] ``#`@@d1_z`. return end @@ -163,13 +171,18 @@ class D3 < D2 def self.get_2 # ^^^^^ definition [..] ``#get_2(). @@d1_v + @@d1_x -# ^^^^^^ reference [..] ``#`@@d1_v`. -# ^^^^^^ reference [..] ``#`@@d1_x`. +# ^^^^^^ reference [..] ``#`@@d1_v`. +# relation definition=[..] ``#`@@d1_v`. +# ^^^^^^ reference [..] ``#`@@d1_x`. +# relation definition=[..] ``#`@@d1_x`. @@d1_y + @@d1_z -# ^^^^^^ reference [..] ``#`@@d1_y`. -# ^^^^^^ reference [..] ``#`@@d1_z`. +# ^^^^^^ reference [..] ``#`@@d1_y`. +# relation definition=[..] ``#`@@d1_y`. +# ^^^^^^ reference [..] ``#`@@d1_z`. +# relation definition=[..] ``#`@@d1_z`. @@d2_x -# ^^^^^^ reference [..] ``#`@@d2_x`. +# ^^^^^^ reference [..] ``#`@@d2_x`. +# relation definition=[..] ``#`@@d2_x`. return end end diff --git a/test/scip/testdata/fields_and_attrs.snapshot.rb b/test/scip/testdata/fields_and_attrs.snapshot.rb index ac6284252..329542264 100644 --- a/test/scip/testdata/fields_and_attrs.snapshot.rb +++ b/test/scip/testdata/fields_and_attrs.snapshot.rb @@ -78,8 +78,10 @@ def self.m1 def m2 # ^^ definition [..] N#m2(). @@b = @@a -# ^^^ definition [..] ``#`@@b`. -# ^^^ reference [..] ``#`@@a`. +# ^^^ definition [..] N#`@@b`. +# relation definition=[..] ``#`@@b`. +# ^^^ reference [..] N#`@@a`. +# relation definition=[..] ``#`@@a`. return end diff --git a/test/scip/testdata/hoverdocs.snapshot.rb b/test/scip/testdata/hoverdocs.snapshot.rb index ccea53e4f..2f12a4603 100644 --- a/test/scip/testdata/hoverdocs.snapshot.rb +++ b/test/scip/testdata/hoverdocs.snapshot.rb @@ -296,16 +296,22 @@ def p1 # | @x (T.untyped) # | ``` @@y = 10 -# ^^^ definition [..] ``#`@@y`. +# ^^^ definition [..] K1#`@@y`. # documentation # | ```ruby # | @@y (T.untyped) # | ``` -# ^^^^^^^^ reference [..] ``#`@@y`. +# relation definition=[..] ``#`@@y`. +# ^^^^^^^^ reference [..] K1#`@@y`. # override_documentation # | ```ruby # | @@y (Integer(10)) # | ``` +# documentation +# | ```ruby +# | @@y (T.untyped) +# | ``` +# relation definition=[..] ``#`@@y`. end # lorem ipsum, you get it @@ -369,25 +375,32 @@ def p1 # documentation # | overrides K1's p1 @x = 20 -# ^^ definition [..] K1#`@x`. +# ^^ definition [..] K2#`@x`. # documentation # | ```ruby # | @x (T.untyped) # | ``` +# relation definition=[..] K1#`@x`. @@y = 20 -# ^^^ definition [..] ``#`@@y`. +# ^^^ definition [..] K2#`@@y`. # documentation # | ```ruby # | @@y (T.untyped) # | ``` +# relation definition=[..] ``#`@@y`. @z += @x # ^^ reference (write) [..] K2#`@z`. # ^^ reference [..] K2#`@z`. # ^^^^^^^^ reference [..] K2#`@z`. -# ^^ reference [..] K1#`@x`. +# ^^ reference [..] K2#`@x`. # override_documentation # | ```ruby # | @x (Integer(20)) # | ``` +# documentation +# | ```ruby +# | @x (T.untyped) +# | ``` +# relation definition=[..] K1#`@x`. end end diff --git a/test/scip/testdata/inheritance.snapshot.rb b/test/scip/testdata/inheritance.snapshot.rb index 62c35269d..80cbd0698 100644 --- a/test/scip/testdata/inheritance.snapshot.rb +++ b/test/scip/testdata/inheritance.snapshot.rb @@ -61,7 +61,8 @@ class Z3 < Z1 def read_f_plus_1? # ^^^^^^^^^^^^^^ definition [..] Z3#`read_f_plus_1?`(). @f + 1 -# ^^ reference [..] Z1#`@f`. +# ^^ reference [..] Z3#`@f`. +# relation definition=[..] Z1#`@f`. end end @@ -80,8 +81,10 @@ def write_f_plus_1(a) # ^^^^^^^ reference [..] Z1#write_f(). # ^ reference local 1~#3337417690 @f = read_f_plus_1? -# ^^ definition [..] Z1#`@f`. -# ^^^^^^^^^^^^^^^^^^^ reference [..] Z1#`@f`. +# ^^ definition [..] Z4#`@f`. +# relation definition=[..] Z1#`@f`. +# ^^^^^^^^^^^^^^^^^^^ reference [..] Z4#`@f`. +# relation definition=[..] Z1#`@f`. # ^^^^^^^^^^^^^^ reference [..] Z3#`read_f_plus_1?`(). end end diff --git a/test/scip/testdata/mixin.rb b/test/scip/testdata/mixin.rb index 7694000e8..4d819aa37 100644 --- a/test/scip/testdata/mixin.rb +++ b/test/scip/testdata/mixin.rb @@ -1,16 +1,12 @@ # typed: true module M - def f - puts 'M.f' - end + def f; puts 'M.f'; end end class C1 include M - def f - puts 'C1.f' - end + def f; puts 'C1.f'; end end # f refers to C1.f @@ -23,9 +19,7 @@ class C3 < C1 end class D1 - def f - puts 'D1.f' - end + def f; puts 'D1.f'; end end class D2 @@ -38,3 +32,443 @@ class D2 D1.new.f # D1.f D2.new.f # M.f + +# Definition in directly included module and Self + +module T0 + module M + def set_f_0; @f = 0; end + end + + class C + include M + def set_f_1; @f = 1; end + def get_f; @f; end + end +end + +# Definition in transitively included module and Self + +module T1 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + include M0 + end + + class C + include M1 + def set_f_1; @f = 1; end + def get_f; @f; end + end +end + +# Definition in directly included module only + +module T2 + module M + def set_f_0; @f = 0; end + end + + class C + include M + def get_f; @f; end + end +end + +# Definition in transitively included module only + +module T3 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + include M0 + end + + class C + include M1 + def get_f; @f; end + end +end + +# Definition in directly included module & superclass & Self + +module T0 + module M + def set_f_0; @f = 0; end + end + + class C0 + def set_f_2; @f = 2; end + end + + class C1 < C0 + include M + def set_f_1; @f = 1; end + def get_f; @f; end + end +end + +# Definition in transitively included module & superclass & Self + +module T3 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + include M0 + end + + class C0 + def set_f_2; @f = 2; end + end + + class C1 < C0 + include M + def set_f_1; @f = 1; end + def get_f; @f; end + end +end + +# Definition in directly included module & superclass only + +module T4 + module M + def set_f_0; @f = 0; end + end + + class C0 + def set_f_1; @f = 1; end + end + + class C1 < C0 + def get_f; @f; end + end +end + +# Definition in transitively included module & superclass only + +module T5 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + include M0 + end + + class C0 + def set_f_1; @f = 1; end + end + + class C1 < C0 + def get_f; @f; end + end +end + +# Definition in module included via superclass & superclass & Self + +module T6 + module M + def set_f_0; @f = 0; end + end + + class C0 + include M + def set_f_1; @f = 1; end + end + + class C1 < C0 + def set_f_2; @f = 2; end + def get_f; @f; end + end +end + +# Definition in module included via superclass & superclass only + +module T7 + module M + def set_f_0; @f = 0; end + end + + class C0 + include M + def set_f_1; @f = 1; end + end + + class C1 < C0 + def get_f; @f; end + end +end + +# Definition in module included via superclass & Self + +module T8 + module M + def set_f_0; @f = 0; end + end + + class C0 + include M + end + + class C1 < C0 + def set_f_2; @f = 2; end + def get_f; @f; end + end +end + +# Definition in module included via superclass only + +module T9 + module M + def set_f_0; @f = 0; end + end + + class C0 + include M + end + + class C1 < C0 + def get_f; @f; end + end +end + +# Definition in multiple transitively included modules & common child & Self + +module T10 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + def set_f_1; @f = 1; end + end + + module M2 + include M0 + include M1 + def set_f_2; @f = 2; end + end + + class C + include M2 + def set_f_3; @f = 3; end + def get_f; @f; end + end +end + +# Definition in multiple transitively included modules & common child only + +module T11 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + def set_f_1; @f = 1; end + end + + module M2 + include M0 + include M1 + def set_f_2; @f = 2; end + end + + class C + include M2 + def get_f; @f; end + end +end + +# Definition in multiple transitively included modules & Self + +module T12 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + def set_f_1; @f = 1; end + end + + module M2 + include M0 + include M1 + end + + class C + include M2 + def set_f_3; @f = 3; end + def get_f; @f; end + end +end + +# Definition in multiple transitively included modules only + +module T13 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + def set_f_1; @f = 1; end + end + + module M2 + include M0 + include M1 + end + + class C + include M2 + def get_f; @f; end + end +end + +# Definition in multiple directly included modules & Self + +module T14 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + def set_f_1; @f = 1; end + end + + class C + include M0 + include M1 + def set_f_2; @f = 2; end + def get_f; @f; end + end +end + +# Definition in multiple directly included modules only + +module T15 + module M0 + def set_f_0; @f = 0; end + end + + module M1 + def set_f_1; @f = 1; end + end + + class C + include M0 + include M1 + def get_f; @f; end + end +end + +# OKAY! Now for the more "weird" situations +# Before this, all the tests had a definition come "before" use. +# Let's see what happens if there is a use before any definition. + +# Reference in directly included module with def in Self + +module W0 + module M + def get_f; @f; end + end + + class C + include M + def set_f; @f = 0; end + end +end + +# Reference in transitively included module with def in Self + +module W1 + module M0 + def get_f; @f; end + end + + module M1 + include M0 + end + + class C + include M1 + def set_f; @f = 0; end + end +end + +# Reference in superclass with def in directly included module + +module W2 + module M + def set_f; @f = 0; end + end + + class C0 + def get_f; @f; end + end + + class C1 < C0 + include M + def get_fp1; @f + 1; end + end +end + +# Reference in directly included module with def in superclass + +module W2 + module M + def get_f; @f; end + end + + class C0 + def set_f; @f = 0; end + end + + class C1 < C0 + include M + def get_fp1; @f + 1; end + end +end + +# Reference in transitively included module with def in in-between module + +module W3 + module M0 + def get_f; @f; end + end + + module M1 + include M0 + def set_f; @f = 0; end + end + + class C + include M1 + def get_fp1; @f + 1; end + end +end + +# Reference in one directly included module with def in other directly included module + +module W4 + module M0 + def get_f; @f; end + end + + module M1 + def set_f; @f + 1; end + end + + class C + include M0 + include M1 + def get_fp1; @f + 1; end + end +end + diff --git a/test/scip/testdata/mixin.snapshot.rb b/test/scip/testdata/mixin.snapshot.rb index 3da652537..d5f5f3b9a 100644 --- a/test/scip/testdata/mixin.snapshot.rb +++ b/test/scip/testdata/mixin.snapshot.rb @@ -2,10 +2,8 @@ module M # ^ definition [..] M# - def f + def f; puts 'M.f'; end # ^ definition [..] M#f(). - puts 'M.f' - end end class C1 @@ -13,11 +11,9 @@ class C1 include M # ^^^^^^^ reference [..] Module#include(). # ^ reference [..] M# - def f + def f; puts 'C1.f'; end # ^ definition [..] C1#f(). - puts 'C1.f' -# ^^^^ reference [..] Kernel#puts(). - end +# ^^^^ reference [..] Kernel#puts(). end # f refers to C1.f @@ -37,11 +33,9 @@ class C3 < C1 class D1 # ^^ definition [..] D1# - def f + def f; puts 'D1.f'; end # ^ definition [..] D1#f(). - puts 'D1.f' -# ^^^^ reference [..] Kernel#puts(). - end +# ^^^^ reference [..] Kernel#puts(). end class D2 @@ -72,3 +66,852 @@ class D2 #^^ reference [..] D2# # ^^^ reference [..] Class#new(). # ^ reference [..] M#f(). + + # Definition in directly included module and Self + + module T0 +# ^^ definition [..] T0# + module M +# ^ definition [..] T0#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T0#M#set_f_0(). +# ^^^^^^^ definition [..] T0#M#set_f_0(). +# ^^ definition [..] T0#M#`@f`. +# ^^^^^^ reference [..] T0#M#`@f`. + end + + class C +# ^ definition [..] T0#C# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T0#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T0#C#set_f_1(). +# ^^ definition [..] T0#C#`@f`. +# relation reference=[..] T0#M#`@f`. +# ^^^^^^ reference [..] T0#C#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T0#C#get_f(). +# ^^ reference [..] T0#C#`@f`. + end + end + + # Definition in transitively included module and Self + + module T1 +# ^^ definition [..] T1# + module M0 +# ^^ definition [..] T1#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T1#M0#set_f_0(). +# ^^ definition [..] T1#M0#`@f`. +# ^^^^^^ reference [..] T1#M0#`@f`. + end + + module M1 +# ^^ definition [..] T1#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T1#M0# + end + + class C +# ^ definition [..] T1#C# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T1#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T1#C#set_f_1(). +# ^^ definition [..] T1#C#`@f`. +# relation reference=[..] T1#M0#`@f`. +# ^^^^^^ reference [..] T1#C#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T1#C#get_f(). +# ^^ reference [..] T1#C#`@f`. + end + end + + # Definition in directly included module only + + module T2 +# ^^ definition [..] T2# + module M +# ^ definition [..] T2#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T2#M#set_f_0(). +# ^^ definition [..] T2#M#`@f`. +# ^^^^^^ reference [..] T2#M#`@f`. + end + + class C +# ^ definition [..] T2#C# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T2#M# + def get_f; @f; end +# ^^^^^ definition [..] T2#C#get_f(). +# ^^ reference [..] T2#C#`@f`. + end + end + + # Definition in transitively included module only + + module T3 +# ^^ definition [..] T3# + module M0 +# ^^ definition [..] T3#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T3#M0#set_f_0(). +# ^^^^^^^ definition [..] T3#M0#set_f_0(). +# ^^ definition [..] T3#M0#`@f`. +# ^^^^^^ reference [..] T3#M0#`@f`. + end + + module M1 +# ^^ definition [..] T3#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T3#M0# + end + + class C +# ^ definition [..] T3#C# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T3#M1# + def get_f; @f; end +# ^^^^^ definition [..] T3#C#get_f(). +# ^^ reference [..] T3#C#`@f`. + end + end + + # Definition in directly included module & superclass & Self + + module T0 +# ^^ definition [..] T0# + module M +# ^ definition [..] T0#M# + def set_f_0; @f = 0; end +# ^^ definition [..] T0#M#`@f`. +# ^^^^^^ reference [..] T0#M#`@f`. + end + + class C0 +# ^^ definition [..] T0#C0# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T0#C0#set_f_2(). +# ^^ definition [..] T0#C0#`@f`. +# ^^^^^^ reference [..] T0#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T0#C1# +# ^^ definition [..] T0#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T0#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T0#C1#set_f_1(). +# ^^ definition [..] T0#C1#`@f`. +# relation definition=[..] T0#C0#`@f`. reference=[..] T0#M#`@f`. +# ^^^^^^ reference [..] T0#C1#`@f`. +# relation definition=[..] T0#C0#`@f`. reference=[..] T0#M#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T0#C1#get_f(). +# ^^ reference [..] T0#C1#`@f`. +# relation definition=[..] T0#C0#`@f`. reference=[..] T0#M#`@f`. + end + end + + # Definition in transitively included module & superclass & Self + + module T3 +# ^^ definition [..] T3# + module M0 +# ^^ definition [..] T3#M0# + def set_f_0; @f = 0; end +# ^^ definition [..] T3#M0#`@f`. +# ^^^^^^ reference [..] T3#M0#`@f`. + end + + module M1 +# ^^ definition [..] T3#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T3#M0# + end + + class C0 +# ^^ definition [..] T3#C0# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T3#C0#set_f_2(). +# ^^ definition [..] T3#C0#`@f`. +# ^^^^^^ reference [..] T3#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T3#C1# +# ^^ definition [..] T3#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T3#C1#set_f_1(). +# ^^ definition [..] T3#C1#`@f`. +# relation definition=[..] T3#C0#`@f`. +# ^^^^^^ reference [..] T3#C1#`@f`. +# relation definition=[..] T3#C0#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T3#C1#get_f(). +# ^^ reference [..] T3#C1#`@f`. +# relation definition=[..] T3#C0#`@f`. + end + end + + # Definition in directly included module & superclass only + + module T4 +# ^^ definition [..] T4# + module M +# ^ definition [..] T4#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T4#M#set_f_0(). +# ^^ definition [..] T4#M#`@f`. +# ^^^^^^ reference [..] T4#M#`@f`. + end + + class C0 +# ^^ definition [..] T4#C0# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T4#C0#set_f_1(). +# ^^ definition [..] T4#C0#`@f`. +# ^^^^^^ reference [..] T4#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T4#C1# +# ^^ definition [..] T4#C0# + def get_f; @f; end +# ^^^^^ definition [..] T4#C1#get_f(). +# ^^ reference [..] T4#C1#`@f`. +# relation definition=[..] T4#C0#`@f`. + end + end + + # Definition in transitively included module & superclass only + + module T5 +# ^^ definition [..] T5# + module M0 +# ^^ definition [..] T5#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T5#M0#set_f_0(). +# ^^ definition [..] T5#M0#`@f`. +# ^^^^^^ reference [..] T5#M0#`@f`. + end + + module M1 +# ^^ definition [..] T5#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T5#M0# + end + + class C0 +# ^^ definition [..] T5#C0# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T5#C0#set_f_1(). +# ^^ definition [..] T5#C0#`@f`. +# ^^^^^^ reference [..] T5#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T5#C1# +# ^^ definition [..] T5#C0# + def get_f; @f; end +# ^^^^^ definition [..] T5#C1#get_f(). +# ^^ reference [..] T5#C1#`@f`. +# relation definition=[..] T5#C0#`@f`. + end + end + + # Definition in module included via superclass & superclass & Self + + module T6 +# ^^ definition [..] T6# + module M +# ^ definition [..] T6#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T6#M#set_f_0(). +# ^^ definition [..] T6#M#`@f`. +# ^^^^^^ reference [..] T6#M#`@f`. + end + + class C0 +# ^^ definition [..] T6#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T6#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T6#C0#set_f_1(). +# ^^ definition [..] T6#C0#`@f`. +# relation reference=[..] T6#M#`@f`. +# ^^^^^^ reference [..] T6#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T6#C1# +# ^^ definition [..] T6#C0# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T6#C1#set_f_2(). +# ^^ definition [..] T6#C1#`@f`. +# relation definition=[..] T6#C0#`@f`. +# ^^^^^^ reference [..] T6#C1#`@f`. +# relation definition=[..] T6#C0#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T6#C1#get_f(). +# ^^ reference [..] T6#C1#`@f`. +# relation definition=[..] T6#C0#`@f`. + end + end + + # Definition in module included via superclass & superclass only + + module T7 +# ^^ definition [..] T7# + module M +# ^ definition [..] T7#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T7#M#set_f_0(). +# ^^ definition [..] T7#M#`@f`. +# ^^^^^^ reference [..] T7#M#`@f`. + end + + class C0 +# ^^ definition [..] T7#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T7#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T7#C0#set_f_1(). +# ^^ definition [..] T7#C0#`@f`. +# relation reference=[..] T7#M#`@f`. +# ^^^^^^ reference [..] T7#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T7#C1# +# ^^ definition [..] T7#C0# + def get_f; @f; end +# ^^^^^ definition [..] T7#C1#get_f(). +# ^^ reference [..] T7#C1#`@f`. +# relation definition=[..] T7#C0#`@f`. + end + end + + # Definition in module included via superclass & Self + + module T8 +# ^^ definition [..] T8# + module M +# ^ definition [..] T8#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T8#M#set_f_0(). +# ^^ definition [..] T8#M#`@f`. +# ^^^^^^ reference [..] T8#M#`@f`. + end + + class C0 +# ^^ definition [..] T8#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T8#M# + end + + class C1 < C0 +# ^^ definition [..] T8#C1# +# ^^ definition [..] T8#C0# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T8#C1#set_f_2(). +# ^^ definition [..] T8#C1#`@f`. +# ^^^^^^ reference [..] T8#C1#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T8#C1#get_f(). +# ^^ reference [..] T8#C1#`@f`. + end + end + + # Definition in module included via superclass only + + module T9 +# ^^ definition [..] T9# + module M +# ^ definition [..] T9#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T9#M#set_f_0(). +# ^^ definition [..] T9#M#`@f`. +# ^^^^^^ reference [..] T9#M#`@f`. + end + + class C0 +# ^^ definition [..] T9#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T9#M# + end + + class C1 < C0 +# ^^ definition [..] T9#C1# +# ^^ definition [..] T9#C0# + def get_f; @f; end +# ^^^^^ definition [..] T9#C1#get_f(). +# ^^ reference [..] T9#C1#`@f`. + end + end + + # Definition in multiple transitively included modules & common child & Self + + module T10 +# ^^^ definition [..] T10# + module M0 +# ^^ definition [..] T10#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T10#M0#set_f_0(). +# ^^ definition [..] T10#M0#`@f`. +# ^^^^^^ reference [..] T10#M0#`@f`. + end + + module M1 +# ^^ definition [..] T10#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T10#M1#set_f_1(). +# ^^ definition [..] T10#M1#`@f`. +# ^^^^^^ reference [..] T10#M1#`@f`. + end + + module M2 +# ^^ definition [..] T10#M2# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T10#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T10#M1# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T10#M2#set_f_2(). +# ^^ definition [..] T10#M2#`@f`. +# relation reference=[..] T10#M0#`@f`. reference=[..] T10#M1#`@f`. +# ^^^^^^ reference [..] T10#M2#`@f`. + end + + class C +# ^ definition [..] T10#C# + include M2 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T10#M2# + def set_f_3; @f = 3; end +# ^^^^^^^ definition [..] T10#C#set_f_3(). +# ^^ definition [..] T10#C#`@f`. +# relation reference=[..] T10#M0#`@f`. reference=[..] T10#M1#`@f`. reference=[..] T10#M2#`@f`. +# ^^^^^^ reference [..] T10#C#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T10#C#get_f(). +# ^^ reference [..] T10#C#`@f`. + end + end + + # Definition in multiple transitively included modules & common child only + + module T11 +# ^^^ definition [..] T11# + module M0 +# ^^ definition [..] T11#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T11#M0#set_f_0(). +# ^^ definition [..] T11#M0#`@f`. +# ^^^^^^ reference [..] T11#M0#`@f`. + end + + module M1 +# ^^ definition [..] T11#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T11#M1#set_f_1(). +# ^^ definition [..] T11#M1#`@f`. +# ^^^^^^ reference [..] T11#M1#`@f`. + end + + module M2 +# ^^ definition [..] T11#M2# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T11#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T11#M1# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T11#M2#set_f_2(). +# ^^ definition [..] T11#M2#`@f`. +# relation reference=[..] T11#M0#`@f`. reference=[..] T11#M1#`@f`. +# ^^^^^^ reference [..] T11#M2#`@f`. + end + + class C +# ^ definition [..] T11#C# + include M2 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T11#M2# + def get_f; @f; end +# ^^^^^ definition [..] T11#C#get_f(). +# ^^ reference [..] T11#C#`@f`. + end + end + + # Definition in multiple transitively included modules & Self + + module T12 +# ^^^ definition [..] T12# + module M0 +# ^^ definition [..] T12#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T12#M0#set_f_0(). +# ^^ definition [..] T12#M0#`@f`. +# ^^^^^^ reference [..] T12#M0#`@f`. + end + + module M1 +# ^^ definition [..] T12#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T12#M1#set_f_1(). +# ^^ definition [..] T12#M1#`@f`. +# ^^^^^^ reference [..] T12#M1#`@f`. + end + + module M2 +# ^^ definition [..] T12#M2# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T12#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T12#M1# + end + + class C +# ^ definition [..] T12#C# + include M2 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T12#M2# + def set_f_3; @f = 3; end +# ^^^^^^^ definition [..] T12#C#set_f_3(). +# ^^ definition [..] T12#C#`@f`. +# relation reference=[..] T12#M0#`@f`. reference=[..] T12#M1#`@f`. +# ^^^^^^ reference [..] T12#C#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T12#C#get_f(). +# ^^ reference [..] T12#C#`@f`. + end + end + + # Definition in multiple transitively included modules only + + module T13 +# ^^^ definition [..] T13# + module M0 +# ^^ definition [..] T13#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T13#M0#set_f_0(). +# ^^ definition [..] T13#M0#`@f`. +# ^^^^^^ reference [..] T13#M0#`@f`. + end + + module M1 +# ^^ definition [..] T13#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T13#M1#set_f_1(). +# ^^ definition [..] T13#M1#`@f`. +# ^^^^^^ reference [..] T13#M1#`@f`. + end + + module M2 +# ^^ definition [..] T13#M2# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T13#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T13#M1# + end + + class C +# ^ definition [..] T13#C# + include M2 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T13#M2# + def get_f; @f; end +# ^^^^^ definition [..] T13#C#get_f(). +# ^^ reference [..] T13#C#`@f`. + end + end + + # Definition in multiple directly included modules & Self + + module T14 +# ^^^ definition [..] T14# + module M0 +# ^^ definition [..] T14#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T14#M0#set_f_0(). +# ^^ definition [..] T14#M0#`@f`. +# ^^^^^^ reference [..] T14#M0#`@f`. + end + + module M1 +# ^^ definition [..] T14#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T14#M1#set_f_1(). +# ^^ definition [..] T14#M1#`@f`. +# ^^^^^^ reference [..] T14#M1#`@f`. + end + + class C +# ^ definition [..] T14#C# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T14#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T14#M1# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T14#C#set_f_2(). +# ^^ definition [..] T14#C#`@f`. +# relation reference=[..] T14#M0#`@f`. reference=[..] T14#M1#`@f`. +# ^^^^^^ reference [..] T14#C#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T14#C#get_f(). +# ^^ reference [..] T14#C#`@f`. + end + end + + # Definition in multiple directly included modules only + + module T15 +# ^^^ definition [..] T15# + module M0 +# ^^ definition [..] T15#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T15#M0#set_f_0(). +# ^^ definition [..] T15#M0#`@f`. +# ^^^^^^ reference [..] T15#M0#`@f`. + end + + module M1 +# ^^ definition [..] T15#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T15#M1#set_f_1(). +# ^^ definition [..] T15#M1#`@f`. +# ^^^^^^ reference [..] T15#M1#`@f`. + end + + class C +# ^ definition [..] T15#C# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T15#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T15#M1# + def get_f; @f; end +# ^^^^^ definition [..] T15#C#get_f(). +# ^^ reference [..] T15#C#`@f`. + end + end + + # OKAY! Now for the more "weird" situations + # Before this, all the tests had a definition come "before" use. + # Let's see what happens if there is a use before any definition. + + # Reference in directly included module with def in Self + + module W0 +# ^^ definition [..] W0# + module M +# ^ definition [..] W0#M# + def get_f; @f; end +# ^^^^^ definition [..] W0#M#get_f(). +# ^^ reference [..] W0#M#`@f`. + end + + class C +# ^ definition [..] W0#C# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] W0#M# + def set_f; @f = 0; end +# ^^^^^ definition [..] W0#C#set_f(). +# ^^ definition [..] W0#C#`@f`. +# relation reference=[..] W0#M#`@f`. +# ^^^^^^ reference [..] W0#C#`@f`. + end + end + + # Reference in transitively included module with def in Self + + module W1 +# ^^ definition [..] W1# + module M0 +# ^^ definition [..] W1#M0# + def get_f; @f; end +# ^^^^^ definition [..] W1#M0#get_f(). +# ^^ reference [..] W1#M0#`@f`. + end + + module M1 +# ^^ definition [..] W1#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W1#M0# + end + + class C +# ^ definition [..] W1#C# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W1#M1# + def set_f; @f = 0; end +# ^^^^^ definition [..] W1#C#set_f(). +# ^^ definition [..] W1#C#`@f`. +# relation reference=[..] W1#M0#`@f`. +# ^^^^^^ reference [..] W1#C#`@f`. + end + end + + # Reference in superclass with def in directly included module + + module W2 +# ^^ definition [..] W2# + module M +# ^ definition [..] W2#M# + def set_f; @f = 0; end +# ^^^^^ definition [..] W2#M#set_f(). +# ^^ definition [..] W2#M#`@f`. +# ^^^^^^ reference [..] W2#M#`@f`. + end + + class C0 +# ^^ definition [..] W2#C0# + def get_f; @f; end +# ^^^^^ definition [..] W2#C0#get_f(). +# ^^ reference [..] W2#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] W2#C1# +# ^^ definition [..] W2#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] W2#M# + def get_fp1; @f + 1; end +# ^^^^^^^ definition [..] W2#C1#get_fp1(). +# ^^^^^^^ definition [..] W2#C1#get_fp1(). +# ^^ reference [..] W2#C1#`@f`. +# relation definition=[..] W2#C0#`@f`. reference=[..] W2#M#`@f`. + end + end + + # Reference in directly included module with def in superclass + + module W2 +# ^^ definition [..] W2# + module M +# ^ definition [..] W2#M# + def get_f; @f; end +# ^^^^^ definition [..] W2#M#get_f(). +# ^^ reference [..] W2#M#`@f`. + end + + class C0 +# ^^ definition [..] W2#C0# + def set_f; @f = 0; end +# ^^^^^ definition [..] W2#C0#set_f(). +# ^^ definition [..] W2#C0#`@f`. +# ^^^^^^ reference [..] W2#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] W2#C1# +# ^^ definition [..] W2#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] W2#M# + def get_fp1; @f + 1; end +# ^^ reference [..] W2#C1#`@f`. +# relation definition=[..] W2#C0#`@f`. reference=[..] W2#M#`@f`. + end + end + + # Reference in transitively included module with def in in-between module + + module W3 +# ^^ definition [..] W3# + module M0 +# ^^ definition [..] W3#M0# + def get_f; @f; end +# ^^^^^ definition [..] W3#M0#get_f(). +# ^^ reference [..] W3#M0#`@f`. + end + + module M1 +# ^^ definition [..] W3#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W3#M0# + def set_f; @f = 0; end +# ^^^^^ definition [..] W3#M1#set_f(). +# ^^ definition [..] W3#M1#`@f`. +# relation reference=[..] W3#M0#`@f`. +# ^^^^^^ reference [..] W3#M1#`@f`. + end + + class C +# ^ definition [..] W3#C# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W3#M1# + def get_fp1; @f + 1; end +# ^^^^^^^ definition [..] W3#C#get_fp1(). +# ^^ reference [..] W3#C#`@f`. + end + end + + # Reference in one directly included module with def in other directly included module + + module W4 +# ^^ definition [..] W4# + module M0 +# ^^ definition [..] W4#M0# + def get_f; @f; end +# ^^^^^ definition [..] W4#M0#get_f(). +# ^^ reference [..] W4#M0#`@f`. + end + + module M1 +# ^^ definition [..] W4#M1# + def set_f; @f + 1; end +# ^^^^^ definition [..] W4#M1#set_f(). +# ^^ reference [..] W4#M1#`@f`. + end + + class C +# ^ definition [..] W4#C# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W4#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W4#M1# + def get_fp1; @f + 1; end +# ^^^^^^^ definition [..] W4#C#get_fp1(). +# ^^ reference [..] W4#C#`@f`. + end + end + diff --git a/test/scip/testdata/type_change.snapshot.rb b/test/scip/testdata/type_change.snapshot.rb index 3db241301..274660950 100644 --- a/test/scip/testdata/type_change.snapshot.rb +++ b/test/scip/testdata/type_change.snapshot.rb @@ -156,11 +156,12 @@ def change_type(b) # | @f (T.untyped) # | ``` @@g = nil -# ^^^ definition [..] ``#`@@g`. +# ^^^ definition [..] C#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` +# relation definition=[..] ``#`@@g`. @k = nil # ^^ definition [..] C#`@k`. # documentation @@ -175,11 +176,16 @@ def change_type(b) # | @f (Integer(1)) # | ``` @@g = 1 -# ^^^ reference (write) [..] ``#`@@g`. +# ^^^ reference (write) [..] C#`@@g`. # override_documentation # | ```ruby # | @@g (Integer(1)) # | ``` +# documentation +# | ```ruby +# | @@g (T.untyped) +# | ``` +# relation definition=[..] ``#`@@g`. @k = 1 # ^^ reference (write) [..] C#`@k`. # override_documentation @@ -199,11 +205,16 @@ def change_type(b) # | @f (String("f")) # | ``` @@g = 'g' -# ^^^ reference (write) [..] ``#`@@g`. +# ^^^ reference (write) [..] C#`@@g`. # override_documentation # | ```ruby # | @@g (String("g")) # | ``` +# documentation +# | ```ruby +# | @@g (T.untyped) +# | ``` +# relation definition=[..] ``#`@@g`. @k = 'k' # ^^ reference (write) [..] C#`@k`. # override_documentation @@ -246,52 +257,68 @@ def change_type(b) # ^ reference [..] BasicObject#`!`(). # ^ reference local 1~#2066187318 @f = 1 -# ^^ definition [..] C#`@f`. +# ^^ definition [..] D#`@f`. # documentation # | ```ruby # | @f (T.untyped) # | ``` +# relation definition=[..] C#`@f`. @@g = 1 -# ^^^ definition [..] ``#`@@g`. +# ^^^ definition [..] D#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` +# relation definition=[..] ``#`@@g`. @k = 1 -# ^^ definition [..] C#`@k`. +# ^^ definition [..] D#`@k`. # documentation # | ```ruby # | @k (T.untyped) # | ``` -# ^^^^^^ reference [..] C#`@k`. +# relation definition=[..] C#`@k`. +# ^^^^^^ reference [..] D#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) # | ``` +# documentation +# | ```ruby +# | @k (T.untyped) +# | ``` +# relation definition=[..] C#`@k`. else @f = 'f' -# ^^ definition [..] C#`@f`. +# ^^ definition [..] D#`@f`. # documentation # | ```ruby # | @f (T.untyped) # | ``` +# relation definition=[..] C#`@f`. @@g = 'g' -# ^^^ definition [..] ``#`@@g`. +# ^^^ definition [..] D#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` +# relation definition=[..] ``#`@@g`. @k = 'k' -# ^^ definition [..] C#`@k`. +# ^^ definition [..] D#`@k`. # documentation # | ```ruby # | @k (T.untyped) # | ``` -# ^^^^^^^^ reference [..] C#`@k`. +# relation definition=[..] C#`@k`. +# ^^^^^^^^ reference [..] D#`@k`. # override_documentation # | ```ruby # | @k (String("k")) # | ``` +# documentation +# | ```ruby +# | @k (T.untyped) +# | ``` +# relation definition=[..] C#`@k`. end end end diff --git a/test/scip_test_runner.cc b/test/scip_test_runner.cc index 26cec1189..e1d7076d3 100644 --- a/test/scip_test_runner.cc +++ b/test/scip_test_runner.cc @@ -219,10 +219,21 @@ void formatSnapshot(const scip::Document &document, FormatOptions options, std:: } }; printDocs(occ.override_documentation(), "override_documentation"); - if (!(isDefinition && symbolTable.contains(occ.symbol()))) { + if (!symbolTable.contains(occ.symbol())) { continue; } auto &symbolInfo = symbolTable[occ.symbol()]; + bool isDefinedByAnother = ([&]() -> bool { + for (auto &rel : symbolInfo.relationships()) { + if (rel.is_definition()) { + return true; + } + } + return false; + })(); + if (!isDefinition && !isDefinedByAnother) { + continue; + } printDocs(symbolInfo.documentation(), "documentation"); relationships.clear(); @@ -233,16 +244,26 @@ void formatSnapshot(const scip::Document &document, FormatOptions options, std:: fast_sort(relationships, [](const scip::Relationship &r1, const scip::Relationship &r2) -> bool { return r1.symbol() < r2.symbol(); }); - for (auto &rel : relationships) { - out << lineStart << "relation " << formatSymbol(rel.symbol()); - if (rel.is_implementation()) { - out << " implementation"; - } - if (rel.is_reference()) { - out << " reference"; - } - if (rel.is_type_definition()) { - out << " type_definition"; + if (!relationships.empty()) { + out << lineStart << "relation "; + for (auto i = 0; i < relationships.size(); ++i) { + auto &rel = relationships[i]; + if (rel.is_implementation()) { + out << "implementation="; + } + if (rel.is_reference()) { + out << "reference="; + } + if (rel.is_type_definition()) { + out << "type_definition="; + } + if (rel.is_definition()) { + out << "definition="; + } + out << formatSymbol(rel.symbol()); + if (i != relationships.size() - 1) { + out << ' '; + } } out << '\n'; } From b0d0c965d2890c4d1c2c0e71226b94cbbb6adebd Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Tue, 27 Sep 2022 10:01:39 +0800 Subject: [PATCH 03/18] fix: Fix incorrect symbols for class vars. --- scip_indexer/SCIPFieldResolve.cc | 16 ++-- scip_indexer/SCIPFieldResolve.h | 3 + scip_indexer/SCIPIndexer.cc | 2 + .../testdata/field_inheritance.snapshot.rb | 2 +- .../testdata/fields_and_attrs.snapshot.rb | 6 +- test/scip/testdata/hoverdocs.snapshot.rb | 40 ++-------- test/scip/testdata/type_change.snapshot.rb | 75 ++++--------------- 7 files changed, 39 insertions(+), 105 deletions(-) diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc index d6a4cf1df..42cd57124 100644 --- a/scip_indexer/SCIPFieldResolve.cc +++ b/scip_indexer/SCIPFieldResolve.cc @@ -62,6 +62,16 @@ void FieldResolver::findUnresolvedFieldInMixinsTransitive(const core::GlobalStat } } +core::ClassOrModuleRef FieldResolver::normalizeParentForClassVar(const core::GlobalState &gs, + core::ClassOrModuleRef klass, std::string_view name) { + auto isClassVar = name.size() >= 2 && name[0] == '@' && name[1] == '@'; + if (isClassVar && !klass.data(gs)->isSingletonClass(gs)) { + // Triggered when undeclared class variables are accessed from instance methods. + return klass.data(gs)->lookupSingletonClass(gs); + } + return klass; +} + FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, FieldQuery query) { auto start = query.start; @@ -75,11 +85,7 @@ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(cons if (isClassInstanceVar) { return FieldQueryResult::Data(start); } - auto isClassVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] == '@'; - if (isClassVar && !start.data(gs)->isSingletonClass(gs)) { - // Triggered when undeclared class variables are accessed from instance methods. - start = start.data(gs)->lookupSingletonClass(gs); - } + start = FieldResolver::normalizeParentForClassVar(gs, start, fieldText); if (gs.unresolvedFields.find(start) == gs.unresolvedFields.end() || !gs.unresolvedFields.find(start)->second.contains(field)) { diff --git a/scip_indexer/SCIPFieldResolve.h b/scip_indexer/SCIPFieldResolve.h index 11f3ce121..52df7c2fd 100644 --- a/scip_indexer/SCIPFieldResolve.h +++ b/scip_indexer/SCIPFieldResolve.h @@ -107,6 +107,9 @@ class FieldResolver final { std::pair findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, FieldQuery query); + static core::ClassOrModuleRef normalizeParentForClassVar(const core::GlobalState &gs, core::ClassOrModuleRef klass, + std::string_view name); + private: void resetMixins(); diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 10f68c157..58acac23b 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -604,6 +604,8 @@ class AliasMap final { switch (result.inherited.kind()) { case FieldQueryResult::Kind::FromUndeclared: { checkExists(result.inherited.originalClass().exists(), "class"); + klass = FieldResolver::normalizeParentForClassVar(gs, klass.asClassOrModuleRef(), + instr->name.shortName(gs)); auto namedSymRef = GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type); if (!cacheHit) { // It may be the case that the mixin values are already stored because of the diff --git a/test/scip/testdata/field_inheritance.snapshot.rb b/test/scip/testdata/field_inheritance.snapshot.rb index 89cf1514d..9d8896eb7 100644 --- a/test/scip/testdata/field_inheritance.snapshot.rb +++ b/test/scip/testdata/field_inheritance.snapshot.rb @@ -29,7 +29,6 @@ def get_inherited_ivar # ^^ reference [..] C2#`@f`. # relation definition=[..] C1#`@f`. # ^^ reference [..] C2#`@h`. -# relation definition=[..] C1#`@h`. end def set_inherited_ivar @@ -65,6 +64,7 @@ def refs # ^^ reference [..] C3#`@g`. # relation definition=[..] C2#`@g`. # ^^ reference [..] C3#`@i`. +# relation definition=[..] C1#`@i`. return end end diff --git a/test/scip/testdata/fields_and_attrs.snapshot.rb b/test/scip/testdata/fields_and_attrs.snapshot.rb index 329542264..ac6284252 100644 --- a/test/scip/testdata/fields_and_attrs.snapshot.rb +++ b/test/scip/testdata/fields_and_attrs.snapshot.rb @@ -78,10 +78,8 @@ def self.m1 def m2 # ^^ definition [..] N#m2(). @@b = @@a -# ^^^ definition [..] N#`@@b`. -# relation definition=[..] ``#`@@b`. -# ^^^ reference [..] N#`@@a`. -# relation definition=[..] ``#`@@a`. +# ^^^ definition [..] ``#`@@b`. +# ^^^ reference [..] ``#`@@a`. return end diff --git a/test/scip/testdata/hoverdocs.snapshot.rb b/test/scip/testdata/hoverdocs.snapshot.rb index 2f12a4603..7f0b7c680 100644 --- a/test/scip/testdata/hoverdocs.snapshot.rb +++ b/test/scip/testdata/hoverdocs.snapshot.rb @@ -296,22 +296,12 @@ def p1 # | @x (T.untyped) # | ``` @@y = 10 -# ^^^ definition [..] K1#`@@y`. -# documentation -# | ```ruby -# | @@y (T.untyped) -# | ``` -# relation definition=[..] ``#`@@y`. -# ^^^^^^^^ reference [..] K1#`@@y`. +# ^^^ definition [..] ``#`@@y`. +# ^^^^^^^^ reference [..] ``#`@@y`. # override_documentation # | ```ruby # | @@y (Integer(10)) # | ``` -# documentation -# | ```ruby -# | @@y (T.untyped) -# | ``` -# relation definition=[..] ``#`@@y`. end # lorem ipsum, you get it @@ -326,10 +316,6 @@ def self.p2 # | lorem ipsum, you get it @z = 10 # ^^ definition [..] ``#`@z`. -# documentation -# | ```ruby -# | @z (T.untyped) -# | ``` # ^^^^^^^ reference [..] ``#`@z`. # override_documentation # | ```ruby @@ -357,12 +343,6 @@ class K2 < K1 # doc comment on class var ooh @z = 9 # ^^ definition [..] ``#`@z`. -# documentation -# | ```ruby -# | @z (T.untyped) -# | ``` -# documentation -# | doc comment on class var ooh # overrides K1's p1 def p1 @@ -376,31 +356,23 @@ def p1 # | overrides K1's p1 @x = 20 # ^^ definition [..] K2#`@x`. -# documentation -# | ```ruby -# | @x (T.untyped) -# | ``` # relation definition=[..] K1#`@x`. @@y = 20 -# ^^^ definition [..] K2#`@@y`. +# ^^^ definition [..] ``#`@@y`. # documentation # | ```ruby # | @@y (T.untyped) # | ``` # relation definition=[..] ``#`@@y`. @z += @x -# ^^ reference (write) [..] K2#`@z`. -# ^^ reference [..] K2#`@z`. -# ^^^^^^^^ reference [..] K2#`@z`. +# ^^ reference (write) [..] ``#`@z`. +# ^^ reference [..] ``#`@z`. +# ^^^^^^^^ reference [..] ``#`@z`. # ^^ reference [..] K2#`@x`. # override_documentation # | ```ruby # | @x (Integer(20)) # | ``` -# documentation -# | ```ruby -# | @x (T.untyped) -# | ``` # relation definition=[..] K1#`@x`. end end diff --git a/test/scip/testdata/type_change.snapshot.rb b/test/scip/testdata/type_change.snapshot.rb index 274660950..72f89df5f 100644 --- a/test/scip/testdata/type_change.snapshot.rb +++ b/test/scip/testdata/type_change.snapshot.rb @@ -132,10 +132,6 @@ class C # | ``` @k = nil # ^^ definition [..] ``#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` def change_type(b) # ^^^^^^^^^^^ definition [..] C#change_type(). @@ -151,23 +147,10 @@ def change_type(b) # | ``` @f = nil # ^^ definition [..] C#`@f`. -# documentation -# | ```ruby -# | @f (T.untyped) -# | ``` @@g = nil -# ^^^ definition [..] C#`@@g`. -# documentation -# | ```ruby -# | @@g (T.untyped) -# | ``` -# relation definition=[..] ``#`@@g`. +# ^^^ definition [..] ``#`@@g`. @k = nil -# ^^ definition [..] C#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` +# ^^ definition [..] ``#`@k`. if b @f = 1 # ^^ reference (write) [..] C#`@f`. @@ -176,23 +159,18 @@ def change_type(b) # | @f (Integer(1)) # | ``` @@g = 1 -# ^^^ reference (write) [..] C#`@@g`. +# ^^^ reference (write) [..] ``#`@@g`. # override_documentation # | ```ruby # | @@g (Integer(1)) # | ``` -# documentation -# | ```ruby -# | @@g (T.untyped) -# | ``` -# relation definition=[..] ``#`@@g`. @k = 1 -# ^^ reference (write) [..] C#`@k`. +# ^^ reference (write) [..] ``#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) # | ``` -# ^^^^^^ reference [..] C#`@k`. +# ^^^^^^ reference [..] ``#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) @@ -205,23 +183,18 @@ def change_type(b) # | @f (String("f")) # | ``` @@g = 'g' -# ^^^ reference (write) [..] C#`@@g`. +# ^^^ reference (write) [..] ``#`@@g`. # override_documentation # | ```ruby # | @@g (String("g")) # | ``` -# documentation -# | ```ruby -# | @@g (T.untyped) -# | ``` -# relation definition=[..] ``#`@@g`. @k = 'k' -# ^^ reference (write) [..] C#`@k`. +# ^^ reference (write) [..] ``#`@k`. # override_documentation # | ```ruby # | @k (String("k")) # | ``` -# ^^^^^^^^ reference [..] C#`@k`. +# ^^^^^^^^ reference [..] ``#`@k`. # override_documentation # | ```ruby # | @k (String("k")) @@ -264,29 +237,19 @@ def change_type(b) # | ``` # relation definition=[..] C#`@f`. @@g = 1 -# ^^^ definition [..] D#`@@g`. +# ^^^ definition [..] ``#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` # relation definition=[..] ``#`@@g`. @k = 1 -# ^^ definition [..] D#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` -# relation definition=[..] C#`@k`. -# ^^^^^^ reference [..] D#`@k`. +# ^^ definition [..] ``#`@k`. +# ^^^^^^ reference [..] ``#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) # | ``` -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` -# relation definition=[..] C#`@k`. else @f = 'f' # ^^ definition [..] D#`@f`. @@ -296,29 +259,19 @@ def change_type(b) # | ``` # relation definition=[..] C#`@f`. @@g = 'g' -# ^^^ definition [..] D#`@@g`. +# ^^^ definition [..] ``#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` # relation definition=[..] ``#`@@g`. @k = 'k' -# ^^ definition [..] D#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` -# relation definition=[..] C#`@k`. -# ^^^^^^^^ reference [..] D#`@k`. +# ^^ definition [..] ``#`@k`. +# ^^^^^^^^ reference [..] ``#`@k`. # override_documentation # | ```ruby # | @k (String("k")) # | ``` -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` -# relation definition=[..] C#`@k`. end end end From 7971edd49a51d166867992ea63bf1466992d90a3 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Tue, 27 Sep 2022 19:53:27 +0800 Subject: [PATCH 04/18] cleanup: Remove debugging print statements. --- scip_indexer/SCIPIndexer.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 58acac23b..4becc8634 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -478,9 +478,6 @@ class SCIPState { void finalizeRefOnlySymbolInfos(const core::GlobalState &gs, core::FileRef file) { auto &potentialSyms = this->potentialRefOnlySymbols[file]; - fmt::print(stderr, "potentialSyms = {}\n", - showSet(potentialSyms, [&](UntypedGenericSymbolRef sym) -> string { return sym.showRaw(gs); })); - fmt::print(stderr, "relMap = {}\n", showRawRelationshipsMap(gs, this->relationshipsMap[file])); for (auto symRef : potentialSyms) { auto valueOrError = this->saveSymbolString(gs, symRef, nullptr); @@ -876,8 +873,6 @@ class CFGTraversal final { void traverse(const cfg::CFG &cfg) { this->aliasMap.populate(this->ctx, cfg, this->scipState.fieldResolver, this->scipState.relationshipsMap[ctx.file]); - fmt::print(stderr, "log: [traverse] relationshipsMap = {}\n", - showRawRelationshipsMap(this->ctx.state, this->scipState.relationshipsMap[ctx.file])); auto &gs = this->ctx.state; auto file = this->ctx.file; auto method = this->ctx.owner; From 2539789cc30ef8f14da2daf23bc17e3b933ef997 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Tue, 27 Sep 2022 21:03:42 +0800 Subject: [PATCH 05/18] fix: Fix non-determinism bug due to bad saveSymbolString API. --- scip_indexer/SCIPIndexer.cc | 49 ++++++++++--------- .../testdata/field_inheritance.snapshot.rb | 1 + 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 4becc8634..674f0c749 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -140,7 +140,6 @@ enum class Emitted { /// so caches should generally directly or indirectly include a FileRef /// as part of key (e.g. via core::Loc). class SCIPState { - string symbolScratchBuffer; UnorderedMap symbolStringCache; /// Cache of occurrences for locals that have been emitted in this function. @@ -202,7 +201,7 @@ class SCIPState { public: SCIPState(GemMetadata metadata) - : symbolScratchBuffer(), symbolStringCache(), localOccurrenceCache(), symbolOccurrenceCache(), + : symbolStringCache(), localOccurrenceCache(), symbolOccurrenceCache(), potentialRefOnlySymbols(), gemMetadata(metadata), occurrenceMap(), emittedSymbols(), symbolMap(), documents(), externalSymbols() {} ~SCIPState() = default; SCIPState(SCIPState &&) = default; @@ -218,31 +217,35 @@ class SCIPState { /// If the returned value is as success, the pointer is non-null. /// /// The argument symbol is used instead of recomputing from scratch if it is non-null. - absl::StatusOr saveSymbolString(const core::GlobalState &gs, UntypedGenericSymbolRef symRef, - const scip::Symbol *symbol) { + absl::Status saveSymbolString(const core::GlobalState &gs, UntypedGenericSymbolRef symRef, + const scip::Symbol *symbol, std::string &output) { auto pair = this->symbolStringCache.find(symRef); if (pair != this->symbolStringCache.end()) { - return &pair->second; + // Yes, there is a "redundant" string copy here when we could "just" + // optimize it to return an interior pointer into the cache. However, + // that creates a footgun where this method cannot be safely called + // across invocations to non-const method calls on SCIPState. + output = pair->second; + return absl::OkStatus(); } - this->symbolScratchBuffer.clear(); - absl::Status status; if (symbol) { - status = scip::utils::emitSymbolString(*symbol, this->symbolScratchBuffer); + status = scip::utils::emitSymbolString(*symbol, output); } else { scip::Symbol symbol; status = symRef.symbolForExpr(gs, this->gemMetadata, {}, symbol); if (!status.ok()) { return status; } - status = scip::utils::emitSymbolString(symbol, this->symbolScratchBuffer); + status = scip::utils::emitSymbolString(symbol, output); } if (!status.ok()) { return status; } - symbolStringCache.insert({symRef, this->symbolScratchBuffer}); - return &symbolStringCache[symRef]; + symbolStringCache.insert({symRef, output}); + + return absl::OkStatus(); } private: @@ -361,9 +364,8 @@ class SCIPState { SmallVec &rels) { untypedSymRef.saveRelationships(gs, this->relationshipsMap[file], rels, [this, &gs](UntypedGenericSymbolRef sym, std::string &out) { - auto status = this->saveSymbolString(gs, sym, nullptr); + auto status = this->saveSymbolString(gs, sym, nullptr, out); ENFORCE(status.ok()); - out = *status.value(); }); } @@ -397,11 +399,11 @@ class SCIPState { if (!status.ok()) { return status; } - absl::StatusOr valueOrStatus(this->saveSymbolString(gs, untypedSymRef, &symbol)); - if (!valueOrStatus.ok()) { - return valueOrStatus.status(); + std::string symbolString; + status = this->saveSymbolString(gs, untypedSymRef, &symbol, symbolString); + if (!status.ok()) { + return status; } - const string &symbolString = *valueOrStatus.value(); SmallVec docs; symRef.saveDocStrings(gs, symRef.definitionType(), occLoc, docs); @@ -444,11 +446,11 @@ class SCIPState { } auto &gs = ctx.state; auto file = ctx.file; - absl::StatusOr valueOrStatus(this->saveSymbolString(gs, symRef.withoutType(), nullptr)); - if (!valueOrStatus.ok()) { - return valueOrStatus.status(); + std::string symbolString; + auto status = this->saveSymbolString(gs, symRef.withoutType(), nullptr, symbolString); + if (!status.ok()) { + return status; } - const string &symbolString = *valueOrStatus.value(); SmallVec overrideDocs{}; using Kind = GenericSymbolRef::Kind; @@ -479,12 +481,11 @@ class SCIPState { void finalizeRefOnlySymbolInfos(const core::GlobalState &gs, core::FileRef file) { auto &potentialSyms = this->potentialRefOnlySymbols[file]; + std::string symbolString; for (auto symRef : potentialSyms) { - auto valueOrError = this->saveSymbolString(gs, symRef, nullptr); - if (!valueOrError.ok()) { + if (!this->saveSymbolString(gs, symRef, nullptr, symbolString).ok()) { continue; } - auto &symbolString = *valueOrError.value(); // Avoid calling saveRelationships if we already emitted this. // saveSymbolInfo does this check too, so it isn't strictly needed. if (this->alreadyEmittedSymbolInfo(file, symbolString)) { diff --git a/test/scip/testdata/field_inheritance.snapshot.rb b/test/scip/testdata/field_inheritance.snapshot.rb index 9d8896eb7..f5dfb02ba 100644 --- a/test/scip/testdata/field_inheritance.snapshot.rb +++ b/test/scip/testdata/field_inheritance.snapshot.rb @@ -29,6 +29,7 @@ def get_inherited_ivar # ^^ reference [..] C2#`@f`. # relation definition=[..] C1#`@f`. # ^^ reference [..] C2#`@h`. +# relation definition=[..] C1#`@h`. end def set_inherited_ivar From 3195c2990ea16af3e3fea55bf903c30ae6f48609 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Tue, 27 Sep 2022 21:05:54 +0800 Subject: [PATCH 06/18] fix: Remove potential non-determinism in relationship ordering. --- scip_indexer/SCIPSymbolRef.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scip_indexer/SCIPSymbolRef.cc b/scip_indexer/SCIPSymbolRef.cc index 39f947287..ada58f1ce 100644 --- a/scip_indexer/SCIPSymbolRef.cc +++ b/scip_indexer/SCIPSymbolRef.cc @@ -10,10 +10,12 @@ #include "absl/strings/str_replace.h" #include "spdlog/fmt/fmt.h" +#include "common/sort.h" #include "core/Loc.h" #include "main/lsp/lsp.h" #include "scip_indexer/Debug.h" +#include "scip_indexer/SCIPProtoExt.h" #include "scip_indexer/SCIPSymbolRef.h" using namespace std; @@ -143,6 +145,8 @@ void UntypedGenericSymbolRef::saveRelationships( rel.set_is_reference(true); saveSymbol(mixin, rel); } + + fast_sort(rels, [](const auto &r1, const auto &r2) -> bool { return scip::compareRelationship(r1, r2) < 0; }); } string GenericSymbolRef::showRaw(const core::GlobalState &gs) const { From 32f16e9efc584631781d81e57e615d29bfd1ab64 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Tue, 27 Sep 2022 21:14:31 +0800 Subject: [PATCH 07/18] cleanup: Simplify test runner logic a bit. --- test/scip_test_runner.cc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/scip_test_runner.cc b/test/scip_test_runner.cc index e1d7076d3..bb46ad2cd 100644 --- a/test/scip_test_runner.cc +++ b/test/scip_test_runner.cc @@ -223,14 +223,8 @@ void formatSnapshot(const scip::Document &document, FormatOptions options, std:: continue; } auto &symbolInfo = symbolTable[occ.symbol()]; - bool isDefinedByAnother = ([&]() -> bool { - for (auto &rel : symbolInfo.relationships()) { - if (rel.is_definition()) { - return true; - } - } - return false; - })(); + bool isDefinedByAnother = + absl::c_any_of(symbolInfo.relationships(), [](const auto &rel) -> bool { return rel.is_definition(); }); if (!isDefinition && !isDefinedByAnother) { continue; } From 41db3aca296c2de9499c264e4bfeab8441258851 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Wed, 28 Sep 2022 09:36:31 +0800 Subject: [PATCH 08/18] fix: Fix incorrect class due to variable mutation. --- scip_indexer/SCIPIndexer.cc | 9 ++++---- test/scip/testdata/hoverdocs.snapshot.rb | 12 +++++++--- test/scip/testdata/type_change.snapshot.rb | 26 ++++++++++++++-------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 674f0c749..e3fc218e5 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -560,7 +560,7 @@ class AliasMap final { this->map = {}; auto &gs = ctx.state; auto method = ctx.owner; - auto klass = method.owner(gs); + const auto klass = method.owner(gs); // Make sure that the offsets we store here match the offsets we use // in saveDefinition/saveReference. auto trim = [&](core::LocOffsets loc) -> core::LocOffsets { @@ -602,9 +602,10 @@ class AliasMap final { switch (result.inherited.kind()) { case FieldQueryResult::Kind::FromUndeclared: { checkExists(result.inherited.originalClass().exists(), "class"); - klass = FieldResolver::normalizeParentForClassVar(gs, klass.asClassOrModuleRef(), - instr->name.shortName(gs)); - auto namedSymRef = GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type); + auto normalizedKlass = FieldResolver::normalizeParentForClassVar( + gs, klass.asClassOrModuleRef(), instr->name.shortName(gs)); + auto namedSymRef = + GenericSymbolRef::undeclaredField(normalizedKlass, instr->name, bind.bind.type); if (!cacheHit) { // It may be the case that the mixin values are already stored because of the // traversal in some other function. In that case, don't bother overriding. diff --git a/test/scip/testdata/hoverdocs.snapshot.rb b/test/scip/testdata/hoverdocs.snapshot.rb index 7f0b7c680..f0b01bcef 100644 --- a/test/scip/testdata/hoverdocs.snapshot.rb +++ b/test/scip/testdata/hoverdocs.snapshot.rb @@ -343,6 +343,12 @@ class K2 < K1 # doc comment on class var ooh @z = 9 # ^^ definition [..] ``#`@z`. +# documentation +# | ```ruby +# | @z (T.untyped) +# | ``` +# documentation +# | doc comment on class var ooh # overrides K1's p1 def p1 @@ -365,9 +371,9 @@ def p1 # | ``` # relation definition=[..] ``#`@@y`. @z += @x -# ^^ reference (write) [..] ``#`@z`. -# ^^ reference [..] ``#`@z`. -# ^^^^^^^^ reference [..] ``#`@z`. +# ^^ reference (write) [..] K2#`@z`. +# ^^ reference [..] K2#`@z`. +# ^^^^^^^^ reference [..] K2#`@z`. # ^^ reference [..] K2#`@x`. # override_documentation # | ```ruby diff --git a/test/scip/testdata/type_change.snapshot.rb b/test/scip/testdata/type_change.snapshot.rb index 72f89df5f..f498fec32 100644 --- a/test/scip/testdata/type_change.snapshot.rb +++ b/test/scip/testdata/type_change.snapshot.rb @@ -132,6 +132,10 @@ class C # | ``` @k = nil # ^^ definition [..] ``#`@k`. +# documentation +# | ```ruby +# | @k (T.untyped) +# | ``` def change_type(b) # ^^^^^^^^^^^ definition [..] C#change_type(). @@ -150,7 +154,7 @@ def change_type(b) @@g = nil # ^^^ definition [..] ``#`@@g`. @k = nil -# ^^ definition [..] ``#`@k`. +# ^^ definition [..] C#`@k`. if b @f = 1 # ^^ reference (write) [..] C#`@f`. @@ -165,12 +169,12 @@ def change_type(b) # | @@g (Integer(1)) # | ``` @k = 1 -# ^^ reference (write) [..] ``#`@k`. +# ^^ reference (write) [..] C#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) # | ``` -# ^^^^^^ reference [..] ``#`@k`. +# ^^^^^^ reference [..] C#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) @@ -189,12 +193,12 @@ def change_type(b) # | @@g (String("g")) # | ``` @k = 'k' -# ^^ reference (write) [..] ``#`@k`. +# ^^ reference (write) [..] C#`@k`. # override_documentation # | ```ruby # | @k (String("k")) # | ``` -# ^^^^^^^^ reference [..] ``#`@k`. +# ^^^^^^^^ reference [..] C#`@k`. # override_documentation # | ```ruby # | @k (String("k")) @@ -244,12 +248,14 @@ def change_type(b) # | ``` # relation definition=[..] ``#`@@g`. @k = 1 -# ^^ definition [..] ``#`@k`. -# ^^^^^^ reference [..] ``#`@k`. +# ^^ definition [..] D#`@k`. +# relation definition=[..] C#`@k`. +# ^^^^^^ reference [..] D#`@k`. # override_documentation # | ```ruby # | @k (Integer(1)) # | ``` +# relation definition=[..] C#`@k`. else @f = 'f' # ^^ definition [..] D#`@f`. @@ -266,12 +272,14 @@ def change_type(b) # | ``` # relation definition=[..] ``#`@@g`. @k = 'k' -# ^^ definition [..] ``#`@k`. -# ^^^^^^^^ reference [..] ``#`@k`. +# ^^ definition [..] D#`@k`. +# relation definition=[..] C#`@k`. +# ^^^^^^^^ reference [..] D#`@k`. # override_documentation # | ```ruby # | @k (String("k")) # | ``` +# relation definition=[..] C#`@k`. end end end From 581c0256a2e937a77741b37f023089ea5cfdc968 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Wed, 28 Sep 2022 09:41:51 +0800 Subject: [PATCH 09/18] cleanup: Remove old FIXME. --- scip_indexer/SCIPFieldResolve.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc index 42cd57124..406e845fd 100644 --- a/scip_indexer/SCIPFieldResolve.cc +++ b/scip_indexer/SCIPFieldResolve.cc @@ -119,7 +119,7 @@ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(cons best = cur; } - if (cur == klass->superClass()) { // FIXME(varun): Handle mix-ins + if (cur == klass->superClass()) { break; } cur = klass->superClass(); From 80ea25970474f70a4e3b0e9690fbc1de7cb8b195 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Wed, 28 Sep 2022 20:13:12 +0800 Subject: [PATCH 10/18] fix: Fix inconsistent owner for declared fields. --- scip_indexer/SCIPIndexer.cc | 17 +++++++++++--- test/scip/testdata/field_inheritance.rb | 14 +++++++++++ .../testdata/field_inheritance.snapshot.rb | 23 +++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index e3fc218e5..07c5024c8 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -632,10 +632,21 @@ class AliasMap final { continue; } if (sym.isFieldOrStaticField()) { + // There are 3 possibilities here. + // 1. This is a reference to a non-instance non-class variable. + // 2. This is a reference to an instance or class variable declared by `klass`. + // 3. This is a reference to an instance or class variable declared by one of `klass`'s ancestor + // classes. + // + // For case 3, we want to emit a scip::Symbol that uses `klass`, not the ancestor. ENFORCE(!bind.loc.empty()); - this->map.insert( - {bind.bind.variable, - {GenericSymbolRef::declaredField(instr->what, bind.bind.type), trim(bind.loc), false}}); + auto name = instr->what.name(gs); + std::string_view nameText = name.shortName(gs); + auto symRef = GenericSymbolRef::declaredField(instr->what, bind.bind.type); + if (!nameText.empty() && nameText[0] == '@' && instr->what.owner(gs) != klass) { + symRef = GenericSymbolRef::undeclaredField(klass, name, bind.bind.type); + } + this->map.insert({bind.bind.variable, {symRef, trim(bind.loc), false}}); continue; } // Outside of definition contexts for classes & modules, diff --git a/test/scip/testdata/field_inheritance.rb b/test/scip/testdata/field_inheritance.rb index c218bd4a1..9710df5a7 100644 --- a/test/scip/testdata/field_inheritance.rb +++ b/test/scip/testdata/field_inheritance.rb @@ -134,3 +134,17 @@ def self.set_y_2 @y = 10 end end + +# Declared fields are inherited the same way as undeclared fields + +class F1 + def initialize + @x = T.let(0, Integer) + end +end + +class F2 + def get_x + @x + end +end diff --git a/test/scip/testdata/field_inheritance.snapshot.rb b/test/scip/testdata/field_inheritance.snapshot.rb index f5dfb02ba..f92d79b86 100644 --- a/test/scip/testdata/field_inheritance.snapshot.rb +++ b/test/scip/testdata/field_inheritance.snapshot.rb @@ -268,3 +268,26 @@ def self.set_y_2 # ^^^^^^^ reference [..] ``#`@y`. end end + + # Declared fields are inherited the same way as undeclared fields + + class F1 +# ^^ definition [..] F1# + def initialize +# ^^^^^^^^^^ definition [..] F1#initialize(). + @x = T.let(0, Integer) +# ^^ definition [..] F1#`@x`. +# ^^^^^^^^^^^^^^^^^^^^^^ reference [..] F1#`@x`. +# ^^^^^^^ definition local 1~#3465713227 +# ^^^^^^^ reference [..] Integer# + end + end + + class F2 +# ^^ definition [..] F2# + def get_x +# ^^^^^ definition [..] F2#get_x(). + @x +# ^^ reference [..] F2#`@x`. + end + end From 9f7e53d8c434d64307e45dcf7c5c882d6d3728f7 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 11:00:00 +0800 Subject: [PATCH 11/18] cleanup: Simplify handling of different kinds of fields. --- scip_indexer/SCIPFieldResolve.cc | 39 +++++------- scip_indexer/SCIPFieldResolve.h | 47 ++------------ scip_indexer/SCIPIndexer.cc | 61 ++++++++----------- scip_indexer/SCIPSymbolRef.cc | 46 ++++---------- scip_indexer/SCIPSymbolRef.h | 38 ++++-------- .../testdata/freeze_constants.snapshot.rb | 14 ++--- 6 files changed, 75 insertions(+), 170 deletions(-) diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc index 406e845fd..2e91fa946 100644 --- a/scip_indexer/SCIPFieldResolve.cc +++ b/scip_indexer/SCIPFieldResolve.cc @@ -14,21 +14,13 @@ using namespace std; namespace sorbet::scip_indexer { -string FieldQueryResult::Data::showRaw(const core::GlobalState &gs) const { - switch (this->kind()) { - case Kind::FromDeclared: - return this->originalSymbol().showRaw(gs); - case Kind::FromUndeclared: - return fmt::format("In({})", absl::StripAsciiWhitespace(this->originalClass().showFullName(gs))); - } -} - string FieldQueryResult::showRaw(const core::GlobalState &gs) const { if (this->mixedIn->empty()) { - return fmt::format("FieldQueryResult(inherited: {})", this->inherited.showRaw(gs)); + return fmt::format("FieldQueryResult(inherited: {})", this->inherited.showFullName(gs)); } - return fmt::format("FieldQueryResult(inherited: {}, mixins: {})", this->inherited.showRaw(gs), - showVec(*this->mixedIn.get(), [&gs](const auto &mixin) -> string { return mixin.showRaw(gs); })); + return fmt::format( + "FieldQueryResult(inherited: {}, mixins: {})", this->inherited.showFullName(gs), + showVec(*this->mixedIn.get(), [&gs](const auto &mixin) -> string { return mixin.showFullName(gs); })); } void FieldResolver::resetMixins() { @@ -41,23 +33,22 @@ void FieldResolver::resetMixins() { // both M1 and M2 will be included in the results (this avoids any kind of postprocessing // of a transitive closure of relationships at the cost of a larger index). void FieldResolver::findUnresolvedFieldInMixinsTransitive(const core::GlobalState &gs, FieldQuery query, - vector &out) { + vector &out) { this->mixinQueue.clear(); for (auto mixin : query.start.data(gs)->mixins()) { this->mixinQueue.push_back(mixin); } auto field = query.field; - using Data = FieldQueryResult::Data; while (auto m = this->mixinQueue.try_pop_front()) { auto mixin = m.value(); auto sym = mixin.data(gs)->findMember(gs, field); if (sym.exists()) { - out.push_back(Data(sym)); + out.push_back(mixin); continue; } auto it = gs.unresolvedFields.find(mixin); if (it != gs.unresolvedFields.end() && it->second.contains(field)) { - out.push_back(Data(mixin)); + out.push_back(mixin); } } } @@ -72,7 +63,7 @@ core::ClassOrModuleRef FieldResolver::normalizeParentForClassVar(const core::Glo return klass; } -FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, +core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, FieldQuery query) { auto start = query.start; auto field = query.field; @@ -83,7 +74,7 @@ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(cons // Class instance variables are not inherited, unlike ordinary instance // variables or class variables. if (isClassInstanceVar) { - return FieldQueryResult::Data(start); + return start; } start = FieldResolver::normalizeParentForClassVar(gs, start, fieldText); @@ -102,7 +93,7 @@ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(cons start.exists() ? start.showFullName(gs) : "")); // As a best-effort guess, assume that the definition is // in this class but we somehow missed it. - return FieldQueryResult::Data(start); + return start; } auto best = start; @@ -112,7 +103,7 @@ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(cons auto sym = klass->findMember(gs, field); if (sym.exists()) { // TODO(varun): Is this early exit justified? // Maybe it is possible to hit this in multiple ancestors? - return FieldQueryResult::Data(sym); + return cur; } auto it = gs.unresolvedFields.find(cur); if (it != gs.unresolvedFields.end() && it->second.contains(field)) { @@ -124,21 +115,21 @@ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain(cons } cur = klass->superClass(); } - return FieldQueryResult::Data(best); + return best; } pair FieldResolver::findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, FieldQuery query) { + ENFORCE(query.field.exists()); auto cacheIt = this->cache.find(query); if (cacheIt != this->cache.end()) { return {cacheIt->second, true}; } auto inherited = this->findUnresolvedFieldInInheritanceChain(gs, loc, query); - using Data = FieldQueryResult::Data; - vector mixins; + vector mixins; findUnresolvedFieldInMixinsTransitive(gs, query, mixins); auto [it, inserted] = - this->cache.insert({query, FieldQueryResult{inherited, make_shared>(move(mixins))}}); + this->cache.insert({query, FieldQueryResult{inherited, make_shared(move(mixins))}}); ENFORCE(inserted); return {it->second, false}; } diff --git a/scip_indexer/SCIPFieldResolve.h b/scip_indexer/SCIPFieldResolve.h index 52df7c2fd..88c446aa1 100644 --- a/scip_indexer/SCIPFieldResolve.h +++ b/scip_indexer/SCIPFieldResolve.h @@ -25,47 +25,8 @@ template H AbslHashValue(H h, const FieldQuery &q) { } struct FieldQueryResult final { - enum class Kind : bool { - FromDeclared, - FromUndeclared, - }; - - class Data { - union Storage { - core::ClassOrModuleRef owner; - core::SymbolRef symbol; - Storage() { - memset(this, 0, sizeof(Storage)); - } - } storage; - Kind _kind; - - public: - Data(Data &&) = default; - Data(const Data &) = default; - Kind kind() const { - return this->_kind; - } - core::ClassOrModuleRef originalClass() const { - ENFORCE(this->kind() == Kind::FromUndeclared); - return this->storage.owner; - } - core::SymbolRef originalSymbol() const { - ENFORCE(this->kind() == Kind::FromUndeclared); - return this->storage.symbol; - } - Data(core::ClassOrModuleRef klass) : _kind(Kind::FromUndeclared) { - this->storage.owner = klass; - } - Data(core::SymbolRef sym) : _kind(Kind::FromDeclared) { - this->storage.symbol = sym; - } - - std::string showRaw(const core::GlobalState &) const; - }; - - Data inherited; - std::shared_ptr> mixedIn; + core::ClassOrModuleRef inherited; + std::shared_ptr> mixedIn; std::string showRaw(const core::GlobalState &gs) const; }; @@ -114,9 +75,9 @@ class FieldResolver final { void resetMixins(); void findUnresolvedFieldInMixinsTransitive(const sorbet::core::GlobalState &gs, FieldQuery query, - std::vector &out); + std::vector &out); - FieldQueryResult::Data findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, + core::ClassOrModuleRef findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, FieldQuery query); }; diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 07c5024c8..04af5c091 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -458,8 +458,7 @@ class SCIPState { case Kind::ClassOrModule: case Kind::Method: break; - case Kind::UndeclaredField: - case Kind::DeclaredField: + case Kind::Field: if (overrideType.has_value()) { symRef.saveDocStrings(gs, overrideType.value(), loc, overrideDocs); } @@ -586,7 +585,7 @@ class AliasMap final { auto klass = core::Symbols::rootSingleton(); this->map.insert( // no trim(...) because globals can't have a :: prefix {bind.bind.variable, - {GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc, false}}); + {GenericSymbolRef::field(klass, instr->name, bind.bind.type), bind.loc, false}}); continue; } auto [result, cacheHit] = fieldResolver.findUnresolvedFieldTransitive( @@ -599,36 +598,18 @@ class AliasMap final { instr->name.exists() ? instr->name.toString(gs) : "", ctx.file.data(gs).path(), ctx.locAt(bind.loc).showRawLineColumn(gs)); }; - switch (result.inherited.kind()) { - case FieldQueryResult::Kind::FromUndeclared: { - checkExists(result.inherited.originalClass().exists(), "class"); - auto normalizedKlass = FieldResolver::normalizeParentForClassVar( - gs, klass.asClassOrModuleRef(), instr->name.shortName(gs)); - auto namedSymRef = - GenericSymbolRef::undeclaredField(normalizedKlass, instr->name, bind.bind.type); - if (!cacheHit) { - // It may be the case that the mixin values are already stored because of the - // traversal in some other function. In that case, don't bother overriding. - relMap.insert({namedSymRef.withoutType(), result}); - } - // no trim(...) because undeclared fields shouldn't have :: - this->map.insert({bind.bind.variable, {namedSymRef, bind.loc, false}}); - break; - } - case FieldQueryResult::Kind::FromDeclared: { - auto fieldSym = result.inherited.originalSymbol(); - checkExists(fieldSym.exists(), "field"); - auto namedSymRef = - fieldSym.owner(gs) == klass - ? GenericSymbolRef::declaredField(fieldSym, bind.bind.type) - : GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type); - if (!cacheHit) { - relMap.insert({namedSymRef.withoutType(), result}); - } - this->map.insert({bind.bind.variable, {namedSymRef, trim(bind.loc), false}}); - break; - } + checkExists(result.inherited.exists(), "class"); + auto normalizedKlass = FieldResolver::normalizeParentForClassVar(gs, klass.asClassOrModuleRef(), + instr->name.shortName(gs)); + auto namedSymRef = GenericSymbolRef::field(normalizedKlass, instr->name, bind.bind.type); + if (!cacheHit) { + // It may be the case that the mixin values are already stored because of the + // traversal in some other function. In that case, don't bother overriding. + relMap.insert({namedSymRef.withoutType(), result}); } + // no trim(...) because undeclared fields shouldn't have :: + ENFORCE(trim(bind.loc) == bind.loc); + this->map.insert({bind.bind.variable, {namedSymRef, bind.loc, false}}); continue; } if (sym.isFieldOrStaticField()) { @@ -642,9 +623,19 @@ class AliasMap final { ENFORCE(!bind.loc.empty()); auto name = instr->what.name(gs); std::string_view nameText = name.shortName(gs); - auto symRef = GenericSymbolRef::declaredField(instr->what, bind.bind.type); - if (!nameText.empty() && nameText[0] == '@' && instr->what.owner(gs) != klass) { - symRef = GenericSymbolRef::undeclaredField(klass, name, bind.bind.type); + auto symRef = GenericSymbolRef::field(instr->what.owner(gs), name, bind.bind.type); + if (!nameText.empty() && nameText[0] == '@') { + if (instr->what.owner(gs) != klass) { + symRef = GenericSymbolRef::field(klass, name, bind.bind.type); + } + // Mimic the logic from the Magic_undeclaredFieldStub branch so that we don't + // miss out on relationships for declared symbols. + if (!relMap.contains(symRef.withoutType())) { + auto [result, _] = fieldResolver.findUnresolvedFieldTransitive( + ctx, ctx.locAt(bind.loc), {klass.asClassOrModuleRef(), name}); + result.inherited = instr->what.owner(gs).asClassOrModuleRef(); + relMap.insert({symRef.withoutType(), result}); + } } this->map.insert({bind.bind.variable, {symRef, trim(bind.loc), false}}); continue; diff --git a/scip_indexer/SCIPSymbolRef.cc b/scip_indexer/SCIPSymbolRef.cc index ada58f1ce..de899d55b 100644 --- a/scip_indexer/SCIPSymbolRef.cc +++ b/scip_indexer/SCIPSymbolRef.cc @@ -24,8 +24,9 @@ namespace sorbet::scip_indexer { string showRawRelationshipsMap(const core::GlobalState &gs, const RelationshipsMap &relMap) { return showMap(relMap, [&gs](const UntypedGenericSymbolRef &ugsr, const auto &result) -> string { - return fmt::format("{}: (inherited={}, mixins={})", ugsr.showRaw(gs), result.inherited.showRaw(gs), - showVec(*result.mixedIn, [&gs](const auto &mixin) -> string { return mixin.showRaw(gs); })); + return fmt::format( + "{}: (inherited={}, mixins={})", ugsr.showRaw(gs), result.inherited.showFullName(gs), + showVec(*result.mixedIn, [&gs](const auto &mixin) -> string { return mixin.showFullName(gs); })); }); } @@ -108,33 +109,18 @@ void UntypedGenericSymbolRef::saveRelationships( if (it == relationshipMap.end()) { return; } - using Kind = FieldQueryResult::Kind; - auto saveSymbol = [&](FieldQueryResult::Data data, scip::Relationship &rel) { - switch (data.kind()) { - case Kind::FromUndeclared: - saveSymbolString(UntypedGenericSymbolRef::undeclared(data.originalClass(), this->name), - *rel.mutable_symbol()); - break; - case Kind::FromDeclared: - saveSymbolString(UntypedGenericSymbolRef::declared(data.originalSymbol()), *rel.mutable_symbol()); - break; + auto saveSymbol = [&](core::ClassOrModuleRef klass, scip::Relationship &rel) { + if (!this->name.exists()) { + fmt::print(stderr, "problematic symbol {}\n", this->selfOrOwner.toStringFullName(gs)); } + saveSymbolString(UntypedGenericSymbolRef::field(klass, this->name), *rel.mutable_symbol()); ENFORCE(!rel.symbol().empty()); rels.push_back(move(rel)); }; auto result = it->second; - bool saveInherited = false; - switch (result.inherited.kind()) { - case Kind::FromUndeclared: - saveInherited = core::SymbolRef(result.inherited.originalClass()) != this->selfOrOwner; - break; - case Kind::FromDeclared: - saveInherited = result.inherited.originalSymbol().owner(gs) != this->selfOrOwner; - } - - if (saveInherited) { + if (core::SymbolRef(result.inherited) != this->selfOrOwner) { scip::Relationship rel; rel.set_is_definition(true); saveSymbol(result.inherited, rel); @@ -151,11 +137,9 @@ void UntypedGenericSymbolRef::saveRelationships( string GenericSymbolRef::showRaw(const core::GlobalState &gs) const { switch (this->kind()) { - case Kind::UndeclaredField: + case Kind::Field: return fmt::format("UndeclaredField(owner: {}, name: {})", this->selfOrOwner.showFullName(gs), this->name.toString(gs)); - case Kind::DeclaredField: - return fmt::format("DeclaredField {}", this->selfOrOwner.showFullName(gs)); case Kind::ClassOrModule: return fmt::format("ClassOrModule {}", this->selfOrOwner.showFullName(gs)); case Kind::Method: @@ -191,19 +175,12 @@ void GenericSymbolRef::saveDocStrings(const core::GlobalState &gs, core::TypePtr string markdown = ""; switch (this->kind()) { - case Kind::UndeclaredField: { + case Kind::Field: { auto name = this->name.show(gs); checkType(fieldType, name); markdown = fmt::format("{} ({})", name, fieldType.show(gs)); break; } - case Kind::DeclaredField: { - auto fieldRef = this->selfOrOwner.asFieldRef(); - auto name = fieldRef.showFullName(gs); - checkType(fieldType, name); - markdown = fmt::format("{} ({})", name, fieldType.show(gs)); - break; - } case Kind::ClassOrModule: { auto ref = this->selfOrOwner.asClassOrModuleRef(); auto classOrModule = ref.data(gs); @@ -252,9 +229,8 @@ core::Loc GenericSymbolRef::symbolLoc(const core::GlobalState &gs) const { return method->nameLoc; } case Kind::ClassOrModule: - case Kind::DeclaredField: return this->selfOrOwner.loc(gs); - case Kind::UndeclaredField: + case Kind::Field: ENFORCE(false, "case UndeclaredField should not be triggered here"); return core::Loc(); } diff --git a/scip_indexer/SCIPSymbolRef.h b/scip_indexer/SCIPSymbolRef.h index af5ad3efe..5763f5b2f 100644 --- a/scip_indexer/SCIPSymbolRef.h +++ b/scip_indexer/SCIPSymbolRef.h @@ -78,12 +78,12 @@ class UntypedGenericSymbolRef final { return H::combine(std::move(h), x.selfOrOwner, x.name); } - static UntypedGenericSymbolRef declared(sorbet::core::SymbolRef sym) { + static UntypedGenericSymbolRef methodOrClassOrModule(sorbet::core::SymbolRef sym) { ENFORCE(sym.exists()); return UntypedGenericSymbolRef(sym, {}); } - static UntypedGenericSymbolRef undeclared(sorbet::core::ClassOrModuleRef klass, sorbet::core::NameRef name) { + static UntypedGenericSymbolRef field(sorbet::core::ClassOrModuleRef klass, sorbet::core::NameRef name) { ENFORCE(klass.exists()); ENFORCE(name.exists()) return UntypedGenericSymbolRef(klass, name); @@ -125,8 +125,7 @@ class GenericSymbolRef final { public: enum class Kind { ClassOrModule, - UndeclaredField, - DeclaredField, + Field, Method, }; @@ -138,11 +137,7 @@ class GenericSymbolRef final { ENFORCE(s.isClassOrModule()); ENFORCE(!n.exists()); return; - case Kind::DeclaredField: - ENFORCE(s.isFieldOrStaticField()); - ENFORCE(!n.exists()); - return; - case Kind::UndeclaredField: + case Kind::Field: ENFORCE(s.isClassOrModule()); ENFORCE(n.exists()); return; @@ -175,12 +170,8 @@ class GenericSymbolRef final { return GenericSymbolRef(self, {}, {}, Kind::ClassOrModule); } - static GenericSymbolRef undeclaredField(core::SymbolRef owner, core::NameRef name, core::TypePtr type) { - return GenericSymbolRef(owner, name, type, Kind::UndeclaredField); - } - - static GenericSymbolRef declaredField(core::SymbolRef self, core::TypePtr type) { - return GenericSymbolRef(self, {}, type, Kind::DeclaredField); + static GenericSymbolRef field(core::SymbolRef owner, core::NameRef name, core::TypePtr type) { + return GenericSymbolRef(owner, name, type, Kind::Field); } static GenericSymbolRef method(core::SymbolRef self) { @@ -193,10 +184,7 @@ class GenericSymbolRef final { Kind kind() const { if (this->name.exists()) { - return Kind::UndeclaredField; - } - if (this->selfOrOwner.isFieldOrStaticField()) { - return Kind::DeclaredField; + return Kind::Field; } if (this->selfOrOwner.isMethod()) { return Kind::Method; @@ -206,13 +194,12 @@ class GenericSymbolRef final { UntypedGenericSymbolRef withoutType() const { switch (this->kind()) { - case Kind::UndeclaredField: + case Kind::Field: ENFORCE(this->selfOrOwner.isClassOrModule()); - return UntypedGenericSymbolRef::undeclared(this->selfOrOwner.asClassOrModuleRef(), this->name); + return UntypedGenericSymbolRef::field(this->selfOrOwner.asClassOrModuleRef(), this->name); case Kind::Method: case Kind::ClassOrModule: - case Kind::DeclaredField: - return UntypedGenericSymbolRef::declared(this->selfOrOwner); + return UntypedGenericSymbolRef::methodOrClassOrModule(this->selfOrOwner); } } @@ -220,7 +207,7 @@ class GenericSymbolRef final { std::string showRaw(const core::GlobalState &gs) const; core::SymbolRef asSymbolRef() const { - ENFORCE(this->kind() != Kind::UndeclaredField); + ENFORCE(this->kind() != Kind::Field); return this->selfOrOwner; } @@ -230,8 +217,7 @@ class GenericSymbolRef final { public: bool isSorbetInternalClassOrMethod(const core::GlobalState &gs) const { switch (this->kind()) { - case Kind::UndeclaredField: - case Kind::DeclaredField: + case Kind::Field: return false; case Kind::ClassOrModule: case Kind::Method: diff --git a/test/scip/testdata/freeze_constants.snapshot.rb b/test/scip/testdata/freeze_constants.snapshot.rb index 0493de295..65a850aa3 100644 --- a/test/scip/testdata/freeze_constants.snapshot.rb +++ b/test/scip/testdata/freeze_constants.snapshot.rb @@ -5,25 +5,25 @@ #^ definition [..] X. #documentation #| ```ruby -#| ::X (T.untyped) +#| X (T.untyped) #| ``` Y = 'Y'.freeze #^ definition [..] Y. #documentation #| ```ruby -#| ::Y (T.untyped) +#| Y (T.untyped) #| ``` A = %w[X Y].freeze #^ definition [..] A. #documentation #| ```ruby -#| ::A (T.untyped) +#| A (T.untyped) #| ``` B = %W[#{X} Y].freeze #^ definition [..] B. #documentation #| ```ruby -#| ::B (T.untyped) +#| B (T.untyped) #| ``` # ^ reference [..] X. @@ -37,19 +37,19 @@ module M # ^ definition [..] M#Z. # documentation # | ```ruby -# | ::M::Z (T.untyped) +# | Z (T.untyped) # | ``` A = %w[X Y Z].freeze # ^ definition [..] M#A. # documentation # | ```ruby -# | ::M::A (T.untyped) +# | A (T.untyped) # | ``` B = %W[#{X} Y Z].freeze # ^ definition [..] M#B. # documentation # | ```ruby -# | ::M::B (T.untyped) +# | B (T.untyped) # | ``` # ^ reference [..] X. end From f11a5c1fee56021ac2bc29fc3ae4d27ccd8aa59e Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 11:17:20 +0800 Subject: [PATCH 12/18] fix: Use FileRef as part of FieldResolver cache. This makes it consistent with other SCIPState caches. There is a risk of higher memory usage but it removes the risk of confusion/incorrect navigation in the presence of unrelated identically-named classes in different files. --- scip_indexer/SCIPFieldResolve.cc | 12 ++++++------ scip_indexer/SCIPFieldResolve.h | 12 +++++++----- scip_indexer/SCIPIndexer.cc | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc index 2e91fa946..b2a809b11 100644 --- a/scip_indexer/SCIPFieldResolve.cc +++ b/scip_indexer/SCIPFieldResolve.cc @@ -63,8 +63,8 @@ core::ClassOrModuleRef FieldResolver::normalizeParentForClassVar(const core::Glo return klass; } -core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, - FieldQuery query) { +core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, + FieldQuery query, core::Loc debugLoc) { auto start = query.start; auto field = query.field; @@ -86,7 +86,7 @@ core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(cons // # blah // end // which is not supported by Sorbet. - LOG_DEBUG(gs, loc, + LOG_DEBUG(gs, debugLoc, fmt::format("couldn't find field {} in class {};\n" "are you using a code pattern like def MyClass.method which is unsupported by Sorbet?", field.exists() ? field.toString(gs) : "", @@ -118,14 +118,14 @@ core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(cons return best; } -pair FieldResolver::findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, - FieldQuery query) { +pair FieldResolver::findUnresolvedFieldTransitive(const core::GlobalState &gs, FieldQuery query, + core::Loc debugLoc) { ENFORCE(query.field.exists()); auto cacheIt = this->cache.find(query); if (cacheIt != this->cache.end()) { return {cacheIt->second, true}; } - auto inherited = this->findUnresolvedFieldInInheritanceChain(gs, loc, query); + auto inherited = this->findUnresolvedFieldInInheritanceChain(gs, query, debugLoc); vector mixins; findUnresolvedFieldInMixinsTransitive(gs, query, mixins); auto [it, inserted] = diff --git a/scip_indexer/SCIPFieldResolve.h b/scip_indexer/SCIPFieldResolve.h index 88c446aa1..68c0f236d 100644 --- a/scip_indexer/SCIPFieldResolve.h +++ b/scip_indexer/SCIPFieldResolve.h @@ -6,22 +6,24 @@ #include #include +#include "core/FileRef.h" #include "core/NameRef.h" #include "core/SymbolRef.h" namespace sorbet::scip_indexer { struct FieldQuery final { + core::FileRef file; sorbet::core::ClassOrModuleRef start; sorbet::core::NameRef field; bool operator==(const FieldQuery &other) const noexcept { - return this->start == other.start && this->field == other.field; + return this->file == other.file && this->start == other.start && this->field == other.field; } }; template H AbslHashValue(H h, const FieldQuery &q) { - return H::combine(std::move(h), q.start, q.field); + return H::combine(std::move(h), q.file, q.start, q.field); } struct FieldQueryResult final { @@ -66,7 +68,7 @@ class FieldResolver final { public: std::pair findUnresolvedFieldTransitive(const core::GlobalState &gs, - core::Loc loc, FieldQuery query); + FieldQuery query, core::Loc debugLoc); static core::ClassOrModuleRef normalizeParentForClassVar(const core::GlobalState &gs, core::ClassOrModuleRef klass, std::string_view name); @@ -77,8 +79,8 @@ class FieldResolver final { void findUnresolvedFieldInMixinsTransitive(const sorbet::core::GlobalState &gs, FieldQuery query, std::vector &out); - core::ClassOrModuleRef findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, core::Loc loc, - FieldQuery query); + core::ClassOrModuleRef findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, FieldQuery query, + core::Loc debugLoc); }; } // namespace sorbet::scip_indexer diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 04af5c091..c52066771 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -589,7 +589,7 @@ class AliasMap final { continue; } auto [result, cacheHit] = fieldResolver.findUnresolvedFieldTransitive( - ctx, ctx.locAt(bind.loc), {klass.asClassOrModuleRef(), instr->name}); + ctx, {ctx.file, klass.asClassOrModuleRef(), instr->name}, ctx.locAt(bind.loc)); auto checkExists = [&](bool exists, const std::string &text) { ENFORCE(exists, "Returned non-existent {} from findUnresolvedFieldTransitive with start={}, " @@ -632,7 +632,7 @@ class AliasMap final { // miss out on relationships for declared symbols. if (!relMap.contains(symRef.withoutType())) { auto [result, _] = fieldResolver.findUnresolvedFieldTransitive( - ctx, ctx.locAt(bind.loc), {klass.asClassOrModuleRef(), name}); + ctx, {ctx.file, klass.asClassOrModuleRef(), name}, ctx.locAt(bind.loc)); result.inherited = instr->what.owner(gs).asClassOrModuleRef(); relMap.insert({symRef.withoutType(), result}); } From 485885f856c76065350c58cdcb94f9532633d30d Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 11:34:28 +0800 Subject: [PATCH 13/18] cleanup: Make logic between undeclared vs declared field more similar. --- scip_indexer/SCIPFieldResolve.cc | 8 ++++---- scip_indexer/SCIPFieldResolve.h | 3 +-- scip_indexer/SCIPIndexer.cc | 31 ++++++++++++++++--------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc index b2a809b11..f76d24351 100644 --- a/scip_indexer/SCIPFieldResolve.cc +++ b/scip_indexer/SCIPFieldResolve.cc @@ -118,12 +118,12 @@ core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(cons return best; } -pair FieldResolver::findUnresolvedFieldTransitive(const core::GlobalState &gs, FieldQuery query, - core::Loc debugLoc) { +FieldQueryResult FieldResolver::findUnresolvedFieldTransitive(const core::GlobalState &gs, FieldQuery query, + core::Loc debugLoc) { ENFORCE(query.field.exists()); auto cacheIt = this->cache.find(query); if (cacheIt != this->cache.end()) { - return {cacheIt->second, true}; + return cacheIt->second; } auto inherited = this->findUnresolvedFieldInInheritanceChain(gs, query, debugLoc); vector mixins; @@ -131,7 +131,7 @@ pair FieldResolver::findUnresolvedFieldTransitive(const auto [it, inserted] = this->cache.insert({query, FieldQueryResult{inherited, make_shared(move(mixins))}}); ENFORCE(inserted); - return {it->second, false}; + return it->second; } } // namespace sorbet::scip_indexer \ No newline at end of file diff --git a/scip_indexer/SCIPFieldResolve.h b/scip_indexer/SCIPFieldResolve.h index 68c0f236d..8d97935fe 100644 --- a/scip_indexer/SCIPFieldResolve.h +++ b/scip_indexer/SCIPFieldResolve.h @@ -67,8 +67,7 @@ class FieldResolver final { BasicQueue mixinQueue; public: - std::pair findUnresolvedFieldTransitive(const core::GlobalState &gs, - FieldQuery query, core::Loc debugLoc); + FieldQueryResult findUnresolvedFieldTransitive(const core::GlobalState &gs, FieldQuery query, core::Loc debugLoc); static core::ClassOrModuleRef normalizeParentForClassVar(const core::GlobalState &gs, core::ClassOrModuleRef klass, std::string_view name); diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index c52066771..71d0a03b1 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -588,23 +588,24 @@ class AliasMap final { {GenericSymbolRef::field(klass, instr->name, bind.bind.type), bind.loc, false}}); continue; } - auto [result, cacheHit] = fieldResolver.findUnresolvedFieldTransitive( - ctx, {ctx.file, klass.asClassOrModuleRef(), instr->name}, ctx.locAt(bind.loc)); - auto checkExists = [&](bool exists, const std::string &text) { - ENFORCE(exists, - "Returned non-existent {} from findUnresolvedFieldTransitive with start={}, " - "field={}, file={}, loc={}", - text, klass.exists() ? klass.toStringFullName(gs) : "", - instr->name.exists() ? instr->name.toString(gs) : "", - ctx.file.data(gs).path(), ctx.locAt(bind.loc).showRawLineColumn(gs)); - }; - checkExists(result.inherited.exists(), "class"); + // There are 4 possibilities here. + // 1. This is an undeclared field logically defined by `klass`. + // 2. This is declared in one of the modules transitively included by `klass`. + // 3. This is an undeclared field logically defined by one of `klass`'s ancestor classes. + // 4. This is an undeclared field logically defined by one of the modules transitively included by + // `klass`. auto normalizedKlass = FieldResolver::normalizeParentForClassVar(gs, klass.asClassOrModuleRef(), instr->name.shortName(gs)); auto namedSymRef = GenericSymbolRef::field(normalizedKlass, instr->name, bind.bind.type); - if (!cacheHit) { - // It may be the case that the mixin values are already stored because of the - // traversal in some other function. In that case, don't bother overriding. + if (!relMap.contains(namedSymRef.withoutType())) { + auto result = fieldResolver.findUnresolvedFieldTransitive( + ctx, {ctx.file, klass.asClassOrModuleRef(), instr->name}, ctx.locAt(bind.loc)); + ENFORCE(result.inherited.exists(), + "Returned non-existent class from findUnresolvedFieldTransitive with start={}, " + "field={}, file={}, loc={}", + klass.exists() ? klass.toStringFullName(gs) : "", + instr->name.exists() ? instr->name.toString(gs) : "", + ctx.file.data(gs).path(), ctx.locAt(bind.loc).showRawLineColumn(gs)) relMap.insert({namedSymRef.withoutType(), result}); } // no trim(...) because undeclared fields shouldn't have :: @@ -631,7 +632,7 @@ class AliasMap final { // Mimic the logic from the Magic_undeclaredFieldStub branch so that we don't // miss out on relationships for declared symbols. if (!relMap.contains(symRef.withoutType())) { - auto [result, _] = fieldResolver.findUnresolvedFieldTransitive( + auto result = fieldResolver.findUnresolvedFieldTransitive( ctx, {ctx.file, klass.asClassOrModuleRef(), name}, ctx.locAt(bind.loc)); result.inherited = instr->what.owner(gs).asClassOrModuleRef(); relMap.insert({symRef.withoutType(), result}); From e9ee6e50831bce0f394a015c4e3f22191ea09643 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 12:28:27 +0800 Subject: [PATCH 14/18] cleanup: Fix repeated names in test case. --- test/scip/testdata/mixin.rb | 37 +-- test/scip/testdata/mixin.snapshot.rb | 479 +++++++++++++-------------- 2 files changed, 257 insertions(+), 259 deletions(-) diff --git a/test/scip/testdata/mixin.rb b/test/scip/testdata/mixin.rb index 4d819aa37..98f96636d 100644 --- a/test/scip/testdata/mixin.rb +++ b/test/scip/testdata/mixin.rb @@ -97,7 +97,7 @@ def get_f; @f; end # Definition in directly included module & superclass & Self -module T0 +module T4 module M def set_f_0; @f = 0; end end @@ -115,7 +115,7 @@ def get_f; @f; end # Definition in transitively included module & superclass & Self -module T3 +module T5 module M0 def set_f_0; @f = 0; end end @@ -137,7 +137,7 @@ def get_f; @f; end # Definition in directly included module & superclass only -module T4 +module T6 module M def set_f_0; @f = 0; end end @@ -153,7 +153,7 @@ def get_f; @f; end # Definition in transitively included module & superclass only -module T5 +module T7 module M0 def set_f_0; @f = 0; end end @@ -173,7 +173,7 @@ def get_f; @f; end # Definition in module included via superclass & superclass & Self -module T6 +module T8 module M def set_f_0; @f = 0; end end @@ -191,7 +191,7 @@ def get_f; @f; end # Definition in module included via superclass & superclass only -module T7 +module T9 module M def set_f_0; @f = 0; end end @@ -208,7 +208,7 @@ def get_f; @f; end # Definition in module included via superclass & Self -module T8 +module T10 module M def set_f_0; @f = 0; end end @@ -225,7 +225,7 @@ def get_f; @f; end # Definition in module included via superclass only -module T9 +module T11 module M def set_f_0; @f = 0; end end @@ -241,7 +241,7 @@ def get_f; @f; end # Definition in multiple transitively included modules & common child & Self -module T10 +module T12 module M0 def set_f_0; @f = 0; end end @@ -265,7 +265,7 @@ def get_f; @f; end # Definition in multiple transitively included modules & common child only -module T11 +module T13 module M0 def set_f_0; @f = 0; end end @@ -288,7 +288,7 @@ def get_f; @f; end # Definition in multiple transitively included modules & Self -module T12 +module T14 module M0 def set_f_0; @f = 0; end end @@ -311,7 +311,7 @@ def get_f; @f; end # Definition in multiple transitively included modules only -module T13 +module T15 module M0 def set_f_0; @f = 0; end end @@ -333,7 +333,7 @@ def get_f; @f; end # Definition in multiple directly included modules & Self -module T14 +module T16 module M0 def set_f_0; @f = 0; end end @@ -352,7 +352,7 @@ def get_f; @f; end # Definition in multiple directly included modules only -module T15 +module T17 module M0 def set_f_0; @f = 0; end end @@ -421,7 +421,7 @@ def get_fp1; @f + 1; end # Reference in directly included module with def in superclass -module W2 +module W3 module M def get_f; @f; end end @@ -438,7 +438,7 @@ def get_fp1; @f + 1; end # Reference in transitively included module with def in in-between module -module W3 +module W4 module M0 def get_f; @f; end end @@ -456,7 +456,7 @@ def get_fp1; @f + 1; end # Reference in one directly included module with def in other directly included module -module W4 +module W5 module M0 def get_f; @f; end end @@ -470,5 +470,4 @@ class C include M1 def get_fp1; @f + 1; end end -end - +end \ No newline at end of file diff --git a/test/scip/testdata/mixin.snapshot.rb b/test/scip/testdata/mixin.snapshot.rb index d5f5f3b9a..dac321db9 100644 --- a/test/scip/testdata/mixin.snapshot.rb +++ b/test/scip/testdata/mixin.snapshot.rb @@ -75,7 +75,6 @@ module M # ^ definition [..] T0#M# def set_f_0; @f = 0; end # ^^^^^^^ definition [..] T0#M#set_f_0(). -# ^^^^^^^ definition [..] T0#M#set_f_0(). # ^^ definition [..] T0#M#`@f`. # ^^^^^^ reference [..] T0#M#`@f`. end @@ -162,7 +161,6 @@ module M0 # ^^ definition [..] T3#M0# def set_f_0; @f = 0; end # ^^^^^^^ definition [..] T3#M0#set_f_0(). -# ^^^^^^^ definition [..] T3#M0#set_f_0(). # ^^ definition [..] T3#M0#`@f`. # ^^^^^^ reference [..] T3#M0#`@f`. end @@ -187,89 +185,6 @@ def get_f; @f; end # Definition in directly included module & superclass & Self - module T0 -# ^^ definition [..] T0# - module M -# ^ definition [..] T0#M# - def set_f_0; @f = 0; end -# ^^ definition [..] T0#M#`@f`. -# ^^^^^^ reference [..] T0#M#`@f`. - end - - class C0 -# ^^ definition [..] T0#C0# - def set_f_2; @f = 2; end -# ^^^^^^^ definition [..] T0#C0#set_f_2(). -# ^^ definition [..] T0#C0#`@f`. -# ^^^^^^ reference [..] T0#C0#`@f`. - end - - class C1 < C0 -# ^^ definition [..] T0#C1# -# ^^ definition [..] T0#C0# - include M -# ^^^^^^^ reference [..] Module#include(). -# ^ reference [..] T0#M# - def set_f_1; @f = 1; end -# ^^^^^^^ definition [..] T0#C1#set_f_1(). -# ^^ definition [..] T0#C1#`@f`. -# relation definition=[..] T0#C0#`@f`. reference=[..] T0#M#`@f`. -# ^^^^^^ reference [..] T0#C1#`@f`. -# relation definition=[..] T0#C0#`@f`. reference=[..] T0#M#`@f`. - def get_f; @f; end -# ^^^^^ definition [..] T0#C1#get_f(). -# ^^ reference [..] T0#C1#`@f`. -# relation definition=[..] T0#C0#`@f`. reference=[..] T0#M#`@f`. - end - end - - # Definition in transitively included module & superclass & Self - - module T3 -# ^^ definition [..] T3# - module M0 -# ^^ definition [..] T3#M0# - def set_f_0; @f = 0; end -# ^^ definition [..] T3#M0#`@f`. -# ^^^^^^ reference [..] T3#M0#`@f`. - end - - module M1 -# ^^ definition [..] T3#M1# - include M0 -# ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T3#M0# - end - - class C0 -# ^^ definition [..] T3#C0# - def set_f_2; @f = 2; end -# ^^^^^^^ definition [..] T3#C0#set_f_2(). -# ^^ definition [..] T3#C0#`@f`. -# ^^^^^^ reference [..] T3#C0#`@f`. - end - - class C1 < C0 -# ^^ definition [..] T3#C1# -# ^^ definition [..] T3#C0# - include M -# ^^^^^^^ reference [..] Module#include(). -# ^ reference [..] M# - def set_f_1; @f = 1; end -# ^^^^^^^ definition [..] T3#C1#set_f_1(). -# ^^ definition [..] T3#C1#`@f`. -# relation definition=[..] T3#C0#`@f`. -# ^^^^^^ reference [..] T3#C1#`@f`. -# relation definition=[..] T3#C0#`@f`. - def get_f; @f; end -# ^^^^^ definition [..] T3#C1#get_f(). -# ^^ reference [..] T3#C1#`@f`. -# relation definition=[..] T3#C0#`@f`. - end - end - - # Definition in directly included module & superclass only - module T4 # ^^ definition [..] T4# module M @@ -282,8 +197,8 @@ def set_f_0; @f = 0; end class C0 # ^^ definition [..] T4#C0# - def set_f_1; @f = 1; end -# ^^^^^^^ definition [..] T4#C0#set_f_1(). + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T4#C0#set_f_2(). # ^^ definition [..] T4#C0#`@f`. # ^^^^^^ reference [..] T4#C0#`@f`. end @@ -291,14 +206,23 @@ def set_f_1; @f = 1; end class C1 < C0 # ^^ definition [..] T4#C1# # ^^ definition [..] T4#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T4#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T4#C1#set_f_1(). +# ^^ definition [..] T4#C1#`@f`. +# relation definition=[..] T4#C0#`@f`. reference=[..] T4#M#`@f`. +# ^^^^^^ reference [..] T4#C1#`@f`. +# relation definition=[..] T4#C0#`@f`. reference=[..] T4#M#`@f`. def get_f; @f; end # ^^^^^ definition [..] T4#C1#get_f(). # ^^ reference [..] T4#C1#`@f`. -# relation definition=[..] T4#C0#`@f`. +# relation definition=[..] T4#C0#`@f`. reference=[..] T4#M#`@f`. end end - # Definition in transitively included module & superclass only + # Definition in transitively included module & superclass & Self module T5 # ^^ definition [..] T5# @@ -319,8 +243,8 @@ module M1 class C0 # ^^ definition [..] T5#C0# - def set_f_1; @f = 1; end -# ^^^^^^^ definition [..] T5#C0#set_f_1(). + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T5#C0#set_f_2(). # ^^ definition [..] T5#C0#`@f`. # ^^^^^^ reference [..] T5#C0#`@f`. end @@ -328,6 +252,15 @@ def set_f_1; @f = 1; end class C1 < C0 # ^^ definition [..] T5#C1# # ^^ definition [..] T5#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T5#C1#set_f_1(). +# ^^ definition [..] T5#C1#`@f`. +# relation definition=[..] T5#C0#`@f`. +# ^^^^^^ reference [..] T5#C1#`@f`. +# relation definition=[..] T5#C0#`@f`. def get_f; @f; end # ^^^^^ definition [..] T5#C1#get_f(). # ^^ reference [..] T5#C1#`@f`. @@ -335,7 +268,7 @@ def get_f; @f; end end end - # Definition in module included via superclass & superclass & Self + # Definition in directly included module & superclass only module T6 # ^^ definition [..] T6# @@ -349,25 +282,15 @@ def set_f_0; @f = 0; end class C0 # ^^ definition [..] T6#C0# - include M -# ^^^^^^^ reference [..] Module#include(). -# ^ reference [..] T6#M# def set_f_1; @f = 1; end # ^^^^^^^ definition [..] T6#C0#set_f_1(). # ^^ definition [..] T6#C0#`@f`. -# relation reference=[..] T6#M#`@f`. # ^^^^^^ reference [..] T6#C0#`@f`. end class C1 < C0 # ^^ definition [..] T6#C1# # ^^ definition [..] T6#C0# - def set_f_2; @f = 2; end -# ^^^^^^^ definition [..] T6#C1#set_f_2(). -# ^^ definition [..] T6#C1#`@f`. -# relation definition=[..] T6#C0#`@f`. -# ^^^^^^ reference [..] T6#C1#`@f`. -# relation definition=[..] T6#C0#`@f`. def get_f; @f; end # ^^^^^ definition [..] T6#C1#get_f(). # ^^ reference [..] T6#C1#`@f`. @@ -375,27 +298,30 @@ def get_f; @f; end end end - # Definition in module included via superclass & superclass only + # Definition in transitively included module & superclass only module T7 # ^^ definition [..] T7# - module M -# ^ definition [..] T7#M# + module M0 +# ^^ definition [..] T7#M0# def set_f_0; @f = 0; end -# ^^^^^^^ definition [..] T7#M#set_f_0(). -# ^^ definition [..] T7#M#`@f`. -# ^^^^^^ reference [..] T7#M#`@f`. +# ^^^^^^^ definition [..] T7#M0#set_f_0(). +# ^^ definition [..] T7#M0#`@f`. +# ^^^^^^ reference [..] T7#M0#`@f`. + end + + module M1 +# ^^ definition [..] T7#M1# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T7#M0# end class C0 # ^^ definition [..] T7#C0# - include M -# ^^^^^^^ reference [..] Module#include(). -# ^ reference [..] T7#M# def set_f_1; @f = 1; end # ^^^^^^^ definition [..] T7#C0#set_f_1(). # ^^ definition [..] T7#C0#`@f`. -# relation reference=[..] T7#M#`@f`. # ^^^^^^ reference [..] T7#C0#`@f`. end @@ -409,7 +335,7 @@ def get_f; @f; end end end - # Definition in module included via superclass & Self + # Definition in module included via superclass & superclass & Self module T8 # ^^ definition [..] T8# @@ -426,6 +352,11 @@ class C0 include M # ^^^^^^^ reference [..] Module#include(). # ^ reference [..] T8#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T8#C0#set_f_1(). +# ^^ definition [..] T8#C0#`@f`. +# relation reference=[..] T8#M#`@f`. +# ^^^^^^ reference [..] T8#C0#`@f`. end class C1 < C0 @@ -434,14 +365,17 @@ class C1 < C0 def set_f_2; @f = 2; end # ^^^^^^^ definition [..] T8#C1#set_f_2(). # ^^ definition [..] T8#C1#`@f`. +# relation definition=[..] T8#C0#`@f`. # ^^^^^^ reference [..] T8#C1#`@f`. +# relation definition=[..] T8#C0#`@f`. def get_f; @f; end # ^^^^^ definition [..] T8#C1#get_f(). # ^^ reference [..] T8#C1#`@f`. +# relation definition=[..] T8#C0#`@f`. end end - # Definition in module included via superclass only + # Definition in module included via superclass & superclass only module T9 # ^^ definition [..] T9# @@ -458,6 +392,11 @@ class C0 include M # ^^^^^^^ reference [..] Module#include(). # ^ reference [..] T9#M# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T9#C0#set_f_1(). +# ^^ definition [..] T9#C0#`@f`. +# relation reference=[..] T9#M#`@f`. +# ^^^^^^ reference [..] T9#C0#`@f`. end class C1 < C0 @@ -466,107 +405,71 @@ class C1 < C0 def get_f; @f; end # ^^^^^ definition [..] T9#C1#get_f(). # ^^ reference [..] T9#C1#`@f`. +# relation definition=[..] T9#C0#`@f`. end end - # Definition in multiple transitively included modules & common child & Self + # Definition in module included via superclass & Self module T10 # ^^^ definition [..] T10# - module M0 -# ^^ definition [..] T10#M0# + module M +# ^ definition [..] T10#M# def set_f_0; @f = 0; end -# ^^^^^^^ definition [..] T10#M0#set_f_0(). -# ^^ definition [..] T10#M0#`@f`. -# ^^^^^^ reference [..] T10#M0#`@f`. +# ^^^^^^^ definition [..] T10#M#set_f_0(). +# ^^ definition [..] T10#M#`@f`. +# ^^^^^^ reference [..] T10#M#`@f`. end - module M1 -# ^^ definition [..] T10#M1# - def set_f_1; @f = 1; end -# ^^^^^^^ definition [..] T10#M1#set_f_1(). -# ^^ definition [..] T10#M1#`@f`. -# ^^^^^^ reference [..] T10#M1#`@f`. - end - - module M2 -# ^^ definition [..] T10#M2# - include M0 -# ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T10#M0# - include M1 + class C0 +# ^^ definition [..] T10#C0# + include M # ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T10#M1# - def set_f_2; @f = 2; end -# ^^^^^^^ definition [..] T10#M2#set_f_2(). -# ^^ definition [..] T10#M2#`@f`. -# relation reference=[..] T10#M0#`@f`. reference=[..] T10#M1#`@f`. -# ^^^^^^ reference [..] T10#M2#`@f`. +# ^ reference [..] T10#M# end - class C -# ^ definition [..] T10#C# - include M2 -# ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T10#M2# - def set_f_3; @f = 3; end -# ^^^^^^^ definition [..] T10#C#set_f_3(). -# ^^ definition [..] T10#C#`@f`. -# relation reference=[..] T10#M0#`@f`. reference=[..] T10#M1#`@f`. reference=[..] T10#M2#`@f`. -# ^^^^^^ reference [..] T10#C#`@f`. + class C1 < C0 +# ^^ definition [..] T10#C1# +# ^^ definition [..] T10#C0# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T10#C1#set_f_2(). +# ^^ definition [..] T10#C1#`@f`. +# ^^^^^^ reference [..] T10#C1#`@f`. def get_f; @f; end -# ^^^^^ definition [..] T10#C#get_f(). -# ^^ reference [..] T10#C#`@f`. +# ^^^^^ definition [..] T10#C1#get_f(). +# ^^ reference [..] T10#C1#`@f`. end end - # Definition in multiple transitively included modules & common child only + # Definition in module included via superclass only module T11 # ^^^ definition [..] T11# - module M0 -# ^^ definition [..] T11#M0# + module M +# ^ definition [..] T11#M# def set_f_0; @f = 0; end -# ^^^^^^^ definition [..] T11#M0#set_f_0(). -# ^^ definition [..] T11#M0#`@f`. -# ^^^^^^ reference [..] T11#M0#`@f`. - end - - module M1 -# ^^ definition [..] T11#M1# - def set_f_1; @f = 1; end -# ^^^^^^^ definition [..] T11#M1#set_f_1(). -# ^^ definition [..] T11#M1#`@f`. -# ^^^^^^ reference [..] T11#M1#`@f`. +# ^^^^^^^ definition [..] T11#M#set_f_0(). +# ^^ definition [..] T11#M#`@f`. +# ^^^^^^ reference [..] T11#M#`@f`. end - module M2 -# ^^ definition [..] T11#M2# - include M0 -# ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T11#M0# - include M1 + class C0 +# ^^ definition [..] T11#C0# + include M # ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T11#M1# - def set_f_2; @f = 2; end -# ^^^^^^^ definition [..] T11#M2#set_f_2(). -# ^^ definition [..] T11#M2#`@f`. -# relation reference=[..] T11#M0#`@f`. reference=[..] T11#M1#`@f`. -# ^^^^^^ reference [..] T11#M2#`@f`. +# ^ reference [..] T11#M# end - class C -# ^ definition [..] T11#C# - include M2 -# ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] T11#M2# + class C1 < C0 +# ^^ definition [..] T11#C1# +# ^^ definition [..] T11#C0# def get_f; @f; end -# ^^^^^ definition [..] T11#C#get_f(). -# ^^ reference [..] T11#C#`@f`. +# ^^^^^ definition [..] T11#C1#get_f(). +# ^^ reference [..] T11#C1#`@f`. end end - # Definition in multiple transitively included modules & Self + # Definition in multiple transitively included modules & common child & Self module T12 # ^^^ definition [..] T12# @@ -594,6 +497,11 @@ module M2 include M1 # ^^^^^^^ reference [..] Module#include(). # ^^ reference [..] T12#M1# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T12#M2#set_f_2(). +# ^^ definition [..] T12#M2#`@f`. +# relation reference=[..] T12#M0#`@f`. reference=[..] T12#M1#`@f`. +# ^^^^^^ reference [..] T12#M2#`@f`. end class C @@ -604,7 +512,7 @@ class C def set_f_3; @f = 3; end # ^^^^^^^ definition [..] T12#C#set_f_3(). # ^^ definition [..] T12#C#`@f`. -# relation reference=[..] T12#M0#`@f`. reference=[..] T12#M1#`@f`. +# relation reference=[..] T12#M0#`@f`. reference=[..] T12#M1#`@f`. reference=[..] T12#M2#`@f`. # ^^^^^^ reference [..] T12#C#`@f`. def get_f; @f; end # ^^^^^ definition [..] T12#C#get_f(). @@ -612,7 +520,7 @@ def get_f; @f; end end end - # Definition in multiple transitively included modules only + # Definition in multiple transitively included modules & common child only module T13 # ^^^ definition [..] T13# @@ -640,6 +548,11 @@ module M2 include M1 # ^^^^^^^ reference [..] Module#include(). # ^^ reference [..] T13#M1# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T13#M2#set_f_2(). +# ^^ definition [..] T13#M2#`@f`. +# relation reference=[..] T13#M0#`@f`. reference=[..] T13#M1#`@f`. +# ^^^^^^ reference [..] T13#M2#`@f`. end class C @@ -653,7 +566,7 @@ def get_f; @f; end end end - # Definition in multiple directly included modules & Self + # Definition in multiple transitively included modules & Self module T14 # ^^^ definition [..] T14# @@ -673,16 +586,23 @@ def set_f_1; @f = 1; end # ^^^^^^ reference [..] T14#M1#`@f`. end - class C -# ^ definition [..] T14#C# + module M2 +# ^^ definition [..] T14#M2# include M0 # ^^^^^^^ reference [..] Module#include(). # ^^ reference [..] T14#M0# include M1 # ^^^^^^^ reference [..] Module#include(). # ^^ reference [..] T14#M1# - def set_f_2; @f = 2; end -# ^^^^^^^ definition [..] T14#C#set_f_2(). + end + + class C +# ^ definition [..] T14#C# + include M2 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T14#M2# + def set_f_3; @f = 3; end +# ^^^^^^^ definition [..] T14#C#set_f_3(). # ^^ definition [..] T14#C#`@f`. # relation reference=[..] T14#M0#`@f`. reference=[..] T14#M1#`@f`. # ^^^^^^ reference [..] T14#C#`@f`. @@ -692,7 +612,7 @@ def get_f; @f; end end end - # Definition in multiple directly included modules only + # Definition in multiple transitively included modules only module T15 # ^^^ definition [..] T15# @@ -712,20 +632,100 @@ def set_f_1; @f = 1; end # ^^^^^^ reference [..] T15#M1#`@f`. end - class C -# ^ definition [..] T15#C# + module M2 +# ^^ definition [..] T15#M2# include M0 # ^^^^^^^ reference [..] Module#include(). # ^^ reference [..] T15#M0# include M1 # ^^^^^^^ reference [..] Module#include(). # ^^ reference [..] T15#M1# + end + + class C +# ^ definition [..] T15#C# + include M2 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T15#M2# def get_f; @f; end # ^^^^^ definition [..] T15#C#get_f(). # ^^ reference [..] T15#C#`@f`. end end + # Definition in multiple directly included modules & Self + + module T16 +# ^^^ definition [..] T16# + module M0 +# ^^ definition [..] T16#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T16#M0#set_f_0(). +# ^^ definition [..] T16#M0#`@f`. +# ^^^^^^ reference [..] T16#M0#`@f`. + end + + module M1 +# ^^ definition [..] T16#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T16#M1#set_f_1(). +# ^^ definition [..] T16#M1#`@f`. +# ^^^^^^ reference [..] T16#M1#`@f`. + end + + class C +# ^ definition [..] T16#C# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T16#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T16#M1# + def set_f_2; @f = 2; end +# ^^^^^^^ definition [..] T16#C#set_f_2(). +# ^^ definition [..] T16#C#`@f`. +# relation reference=[..] T16#M0#`@f`. reference=[..] T16#M1#`@f`. +# ^^^^^^ reference [..] T16#C#`@f`. + def get_f; @f; end +# ^^^^^ definition [..] T16#C#get_f(). +# ^^ reference [..] T16#C#`@f`. + end + end + + # Definition in multiple directly included modules only + + module T17 +# ^^^ definition [..] T17# + module M0 +# ^^ definition [..] T17#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T17#M0#set_f_0(). +# ^^ definition [..] T17#M0#`@f`. +# ^^^^^^ reference [..] T17#M0#`@f`. + end + + module M1 +# ^^ definition [..] T17#M1# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T17#M1#set_f_1(). +# ^^ definition [..] T17#M1#`@f`. +# ^^^^^^ reference [..] T17#M1#`@f`. + end + + class C +# ^ definition [..] T17#C# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T17#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T17#M1# + def get_f; @f; end +# ^^^^^ definition [..] T17#C#get_f(). +# ^^ reference [..] T17#C#`@f`. + end + end + # OKAY! Now for the more "weird" situations # Before this, all the tests had a definition come "before" use. # Let's see what happens if there is a use before any definition. @@ -812,7 +812,6 @@ class C1 < C0 # ^ reference [..] W2#M# def get_fp1; @f + 1; end # ^^^^^^^ definition [..] W2#C1#get_fp1(). -# ^^^^^^^ definition [..] W2#C1#get_fp1(). # ^^ reference [..] W2#C1#`@f`. # relation definition=[..] W2#C0#`@f`. reference=[..] W2#M#`@f`. end @@ -820,98 +819,98 @@ def get_fp1; @f + 1; end # Reference in directly included module with def in superclass - module W2 -# ^^ definition [..] W2# + module W3 +# ^^ definition [..] W3# module M -# ^ definition [..] W2#M# +# ^ definition [..] W3#M# def get_f; @f; end -# ^^^^^ definition [..] W2#M#get_f(). -# ^^ reference [..] W2#M#`@f`. +# ^^^^^ definition [..] W3#M#get_f(). +# ^^ reference [..] W3#M#`@f`. end class C0 -# ^^ definition [..] W2#C0# +# ^^ definition [..] W3#C0# def set_f; @f = 0; end -# ^^^^^ definition [..] W2#C0#set_f(). -# ^^ definition [..] W2#C0#`@f`. -# ^^^^^^ reference [..] W2#C0#`@f`. +# ^^^^^ definition [..] W3#C0#set_f(). +# ^^ definition [..] W3#C0#`@f`. +# ^^^^^^ reference [..] W3#C0#`@f`. end class C1 < C0 -# ^^ definition [..] W2#C1# -# ^^ definition [..] W2#C0# +# ^^ definition [..] W3#C1# +# ^^ definition [..] W3#C0# include M # ^^^^^^^ reference [..] Module#include(). -# ^ reference [..] W2#M# +# ^ reference [..] W3#M# def get_fp1; @f + 1; end -# ^^ reference [..] W2#C1#`@f`. -# relation definition=[..] W2#C0#`@f`. reference=[..] W2#M#`@f`. +# ^^^^^^^ definition [..] W3#C1#get_fp1(). +# ^^ reference [..] W3#C1#`@f`. +# relation definition=[..] W3#C0#`@f`. reference=[..] W3#M#`@f`. end end # Reference in transitively included module with def in in-between module - module W3 -# ^^ definition [..] W3# + module W4 +# ^^ definition [..] W4# module M0 -# ^^ definition [..] W3#M0# +# ^^ definition [..] W4#M0# def get_f; @f; end -# ^^^^^ definition [..] W3#M0#get_f(). -# ^^ reference [..] W3#M0#`@f`. +# ^^^^^ definition [..] W4#M0#get_f(). +# ^^ reference [..] W4#M0#`@f`. end module M1 -# ^^ definition [..] W3#M1# +# ^^ definition [..] W4#M1# include M0 # ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] W3#M0# +# ^^ reference [..] W4#M0# def set_f; @f = 0; end -# ^^^^^ definition [..] W3#M1#set_f(). -# ^^ definition [..] W3#M1#`@f`. -# relation reference=[..] W3#M0#`@f`. -# ^^^^^^ reference [..] W3#M1#`@f`. +# ^^^^^ definition [..] W4#M1#set_f(). +# ^^ definition [..] W4#M1#`@f`. +# relation reference=[..] W4#M0#`@f`. +# ^^^^^^ reference [..] W4#M1#`@f`. end class C -# ^ definition [..] W3#C# +# ^ definition [..] W4#C# include M1 # ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] W3#M1# +# ^^ reference [..] W4#M1# def get_fp1; @f + 1; end -# ^^^^^^^ definition [..] W3#C#get_fp1(). -# ^^ reference [..] W3#C#`@f`. +# ^^^^^^^ definition [..] W4#C#get_fp1(). +# ^^ reference [..] W4#C#`@f`. end end # Reference in one directly included module with def in other directly included module - module W4 -# ^^ definition [..] W4# + module W5 +# ^^ definition [..] W5# module M0 -# ^^ definition [..] W4#M0# +# ^^ definition [..] W5#M0# def get_f; @f; end -# ^^^^^ definition [..] W4#M0#get_f(). -# ^^ reference [..] W4#M0#`@f`. +# ^^^^^ definition [..] W5#M0#get_f(). +# ^^ reference [..] W5#M0#`@f`. end module M1 -# ^^ definition [..] W4#M1# +# ^^ definition [..] W5#M1# def set_f; @f + 1; end -# ^^^^^ definition [..] W4#M1#set_f(). -# ^^ reference [..] W4#M1#`@f`. +# ^^^^^ definition [..] W5#M1#set_f(). +# ^^ reference [..] W5#M1#`@f`. end class C -# ^ definition [..] W4#C# +# ^ definition [..] W5#C# include M0 # ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] W4#M0# +# ^^ reference [..] W5#M0# include M1 # ^^^^^^^ reference [..] Module#include(). -# ^^ reference [..] W4#M1# +# ^^ reference [..] W5#M1# def get_fp1; @f + 1; end -# ^^^^^^^ definition [..] W4#C#get_fp1(). -# ^^ reference [..] W4#C#`@f`. +# ^^^^^^^ definition [..] W5#C#get_fp1(). +# ^^ reference [..] W5#C#`@f`. end end - From df144e1548c762364bbf381a5e7c3c1ce75aebc1 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 12:40:56 +0800 Subject: [PATCH 15/18] test: Add test for globals. --- test/scip/testdata/globals.rb | 6 ++++++ test/scip/testdata/globals.snapshot.rb | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/test/scip/testdata/globals.rb b/test/scip/testdata/globals.rb index 0c487a23a..00080d0e2 100644 --- a/test/scip/testdata/globals.rb +++ b/test/scip/testdata/globals.rb @@ -16,3 +16,9 @@ def g end puts $c + +$d = T.let(0, Integer) + +def g + $d +end diff --git a/test/scip/testdata/globals.snapshot.rb b/test/scip/testdata/globals.snapshot.rb index 227bddcd9..38560c5e7 100644 --- a/test/scip/testdata/globals.snapshot.rb +++ b/test/scip/testdata/globals.snapshot.rb @@ -30,3 +30,14 @@ def g puts $c #^^^^ reference [..] Kernel#puts(). # ^^ reference [..] `>`#$c. + + $d = T.let(0, Integer) +#^^ definition [..] `>`#$d. +# ^^^^^^^ definition local 3~#119448696 +# ^^^^^^^ reference [..] Integer# + + def g +# ^ definition [..] Object#g(). + $d +# ^^ reference [..] `>`#$d. + end From 7560111895073bc00dfcfcaf0944582b2c007299 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 14:04:24 +0800 Subject: [PATCH 16/18] fix: Fix bug in handling of declared class vars. --- scip_indexer/SCIPIndexer.cc | 9 ++++---- test/scip/testdata/field_inheritance.rb | 12 ++++++++++ .../testdata/field_inheritance.snapshot.rb | 22 +++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 71d0a03b1..a324ef609 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -626,15 +626,16 @@ class AliasMap final { std::string_view nameText = name.shortName(gs); auto symRef = GenericSymbolRef::field(instr->what.owner(gs), name, bind.bind.type); if (!nameText.empty() && nameText[0] == '@') { - if (instr->what.owner(gs) != klass) { - symRef = GenericSymbolRef::field(klass, name, bind.bind.type); - } + auto normalizedKlass = + FieldResolver::normalizeParentForClassVar(gs, klass.asClassOrModuleRef(), nameText); + symRef = GenericSymbolRef::field(normalizedKlass, name, bind.bind.type); // Mimic the logic from the Magic_undeclaredFieldStub branch so that we don't // miss out on relationships for declared symbols. if (!relMap.contains(symRef.withoutType())) { auto result = fieldResolver.findUnresolvedFieldTransitive( ctx, {ctx.file, klass.asClassOrModuleRef(), name}, ctx.locAt(bind.loc)); - result.inherited = instr->what.owner(gs).asClassOrModuleRef(); + result.inherited = + FieldResolver::normalizeParentForClassVar(gs, result.inherited, nameText); relMap.insert({symRef.withoutType(), result}); } } diff --git a/test/scip/testdata/field_inheritance.rb b/test/scip/testdata/field_inheritance.rb index 9710df5a7..0f66f9005 100644 --- a/test/scip/testdata/field_inheritance.rb +++ b/test/scip/testdata/field_inheritance.rb @@ -106,6 +106,18 @@ def f return end +## Check that pre-declared class variables work too + +class DD1 + @@x = T.let(0, Integer) +end + +class DD2 < DD1 + def self.get_x + @@x + end +end + # Class instance variables are not inherited. class E1 diff --git a/test/scip/testdata/field_inheritance.snapshot.rb b/test/scip/testdata/field_inheritance.snapshot.rb index f92d79b86..4703f3ff9 100644 --- a/test/scip/testdata/field_inheritance.snapshot.rb +++ b/test/scip/testdata/field_inheritance.snapshot.rb @@ -224,6 +224,28 @@ def f return end + ## Check that pre-declared class variables work too + + class DD1 +# ^^^ definition [..] DD1# + @@x = T.let(0, Integer) +# ^^^ definition [..] ``#`@@x`. +# ^^^^^^^^^^^^^^^^^^^^^^^ reference [..] ``#`@@x`. +# ^^^^^^^ definition local 1~#119448696 +# ^^^^^^^ reference [..] Integer# + end + + class DD2 < DD1 +# ^^^ definition [..] DD2# +# ^^^ definition [..] DD1# + def self.get_x +# ^^^^^ definition [..] ``#get_x(). + @@x +# ^^^ reference [..] ``#`@@x`. +# relation definition=[..] ``#`@@x`. + end + end + # Class instance variables are not inherited. class E1 From 897e5f8c082f1b539e05600a0949721389970120 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 14:04:54 +0800 Subject: [PATCH 17/18] debug: Generalize signature of showVec to allow InlinedVector. --- scip_indexer/Debug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scip_indexer/Debug.h b/scip_indexer/Debug.h index 21a77dcc1..d7661c671 100644 --- a/scip_indexer/Debug.h +++ b/scip_indexer/Debug.h @@ -42,7 +42,7 @@ template std::string showSet(const sorbet::UnorderedSe return out.str(); } -template std::string showVec(const std::vector &v, Fn f) { +template std::string showVec(const V &v, Fn f) { std::ostringstream out; out << "["; for (auto i = 0; i < v.size(); ++i) { From ff24725852a80ad3102e02641a88a8d045afa5e5 Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Thu, 29 Sep 2022 14:05:34 +0800 Subject: [PATCH 18/18] debug: Add helper method to print scip::Relationship. --- scip_indexer/SCIPProtoExt.cc | 24 ++++++++++++++++++++++++ scip_indexer/SCIPProtoExt.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/scip_indexer/SCIPProtoExt.cc b/scip_indexer/SCIPProtoExt.cc index dd4819730..39cfc26a6 100644 --- a/scip_indexer/SCIPProtoExt.cc +++ b/scip_indexer/SCIPProtoExt.cc @@ -1,5 +1,11 @@ #include "scip_indexer/SCIPProtoExt.h" +// Include first because it uses poisoned identifiers. +#include "spdlog/fmt/fmt.h" + +#include "scip_indexer/Debug.h" + +#include #include namespace scip { @@ -68,4 +74,22 @@ int compareSymbolInformation(const scip::SymbolInformation &s1, const scip::Symb return 0; } #undef CHECK_CMP + +std::string showRawRelationship(const scip::Relationship &rel) { + std::vector info; + if (rel.is_reference()) { + info.push_back("ref"); + } + if (rel.is_implementation()) { + info.push_back("impl"); + } + if (rel.is_type_definition()) { + info.push_back("type_def"); + } + if (rel.is_definition()) { + info.push_back("def"); + } + return fmt::format("Rel(to: {}, {})", rel.symbol(), + sorbet::scip_indexer::showVec(info, [](const auto &s) { return s; })); +} } // namespace scip \ No newline at end of file diff --git a/scip_indexer/SCIPProtoExt.h b/scip_indexer/SCIPProtoExt.h index 8eaa03f93..9847fc549 100644 --- a/scip_indexer/SCIPProtoExt.h +++ b/scip_indexer/SCIPProtoExt.h @@ -9,6 +9,8 @@ int compareOccurrence(const scip::Occurrence &o1, const scip::Occurrence &o2); int compareRelationship(const scip::Relationship &r1, const scip::Relationship &r2); int compareSymbolInformation(const scip::SymbolInformation &s1, const scip::SymbolInformation &s2); + +std::string showRawRelationship(const scip::Relationship &rel); } // namespace scip #endif // SORBET_SCIP_PROTO_EXT \ No newline at end of file