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/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) { diff --git a/scip_indexer/SCIPFieldResolve.cc b/scip_indexer/SCIPFieldResolve.cc new file mode 100644 index 000000000..f76d24351 --- /dev/null +++ b/scip_indexer/SCIPFieldResolve.cc @@ -0,0 +1,137 @@ +#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::showRaw(const core::GlobalState &gs) const { + if (this->mixedIn->empty()) { + return fmt::format("FieldQueryResult(inherited: {})", this->inherited.showFullName(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() { + 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; + 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(mixin); + continue; + } + auto it = gs.unresolvedFields.find(mixin); + if (it != gs.unresolvedFields.end() && it->second.contains(field)) { + out.push_back(mixin); + } + } +} + +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; +} + +core::ClassOrModuleRef FieldResolver::findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, + FieldQuery query, core::Loc debugLoc) { + 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 start; + } + start = FieldResolver::normalizeParentForClassVar(gs, start, fieldText); + + 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, 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) : "", + 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()) { // TODO(varun): Is this early exit justified? + // Maybe it is possible to hit this in multiple ancestors? + return cur; + } + auto it = gs.unresolvedFields.find(cur); + if (it != gs.unresolvedFields.end() && it->second.contains(field)) { + best = cur; + } + + if (cur == klass->superClass()) { + break; + } + cur = klass->superClass(); + } + return best; +} + +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; + } + auto inherited = this->findUnresolvedFieldInInheritanceChain(gs, query, debugLoc); + vector mixins; + findUnresolvedFieldInMixinsTransitive(gs, query, mixins); + auto [it, inserted] = + this->cache.insert({query, FieldQueryResult{inherited, make_shared(move(mixins))}}); + ENFORCE(inserted); + 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 new file mode 100644 index 000000000..8d97935fe --- /dev/null +++ b/scip_indexer/SCIPFieldResolve.h @@ -0,0 +1,87 @@ + +#ifndef SORBET_SCIP_FIELD_RESOLVE +#define SORBET_SCIP_FIELD_RESOLVE + +#include +#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->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.file, q.start, q.field); +} + +struct FieldQueryResult final { + core::ClassOrModuleRef 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: + 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); + +private: + void resetMixins(); + + void findUnresolvedFieldInMixinsTransitive(const sorbet::core::GlobalState &gs, FieldQuery query, + std::vector &out); + + core::ClassOrModuleRef findUnresolvedFieldInInheritanceChain(const core::GlobalState &gs, FieldQuery query, + core::Loc debugLoc); +}; + +} // 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..a324ef609 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,8 +135,11 @@ 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; /// Cache of occurrences for locals that have been emitted in this function. @@ -153,6 +157,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,12 +187,21 @@ 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; public: SCIPState(GemMetadata metadata) - : symbolScratchBuffer(), symbolStringCache(), localOccurrenceCache(), symbolOccurrenceCache(), + : symbolStringCache(), localOccurrenceCache(), symbolOccurrenceCache(), potentialRefOnlySymbols(), gemMetadata(metadata), occurrenceMap(), emittedSymbols(), symbolMap(), documents(), externalSymbols() {} ~SCIPState() = default; SCIPState(SCIPState &&) = default; @@ -195,36 +217,45 @@ 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: - 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 +263,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 +360,15 @@ 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, out); + ENFORCE(status.ok()); + }); + } + 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 +381,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,19 +394,24 @@ 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)); - 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); - 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, @@ -397,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; @@ -409,16 +458,44 @@ 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); } } + + // 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]; + + std::string symbolString; + for (auto symRef : potentialSyms) { + if (!this->saveSymbolString(gs, symRef, nullptr, symbolString).ok()) { + continue; + } + // 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 +540,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,11 +554,12 @@ 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; - 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 { @@ -564,36 +585,61 @@ 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 = 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}}); - } - } 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}}); - } - } else { - ENFORCE(false, "Should've handled all cases of variant earlier"); + // 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 (!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 :: + ENFORCE(trim(bind.loc) == bind.loc); + this->map.insert({bind.bind.variable, {namedSymRef, bind.loc, false}}); 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::field(instr->what.owner(gs), name, bind.bind.type); + if (!nameText.empty() && nameText[0] == '@') { + 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 = + FieldResolver::normalizeParentForClassVar(gs, result.inherited, nameText); + relMap.insert({symRef.withoutType(), result}); + } + } + this->map.insert({bind.bind.variable, {symRef, trim(bind.loc), false}}); continue; } // Outside of definition contexts for classes & modules, @@ -831,7 +877,8 @@ 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]); auto &gs = this->ctx.state; auto file = this->ctx.file; auto method = this->ctx.owner; @@ -1100,7 +1147,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/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 diff --git a/scip_indexer/SCIPSymbolRef.cc b/scip_indexer/SCIPSymbolRef.cc index 4d1db0117..de899d55b 100644 --- a/scip_indexer/SCIPSymbolRef.cc +++ b/scip_indexer/SCIPSymbolRef.cc @@ -6,17 +6,30 @@ #include #include "absl/status/status.h" +#include "absl/strings/ascii.h" #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; 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.showFullName(gs), + showVec(*result.mixedIn, [&gs](const auto &mixin) -> string { return mixin.showFullName(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,18 +96,50 @@ 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(symbol: {})", this->selfOrOwner.showFullName(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; + } + 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; + + if (core::SymbolRef(result.inherited) != this->selfOrOwner) { + 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); + } + + fast_sort(rels, [](const auto &r1, const auto &r2) -> bool { return scip::compareRelationship(r1, r2) < 0; }); } 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: @@ -130,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); @@ -191,12 +229,11 @@ 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(); } } -} // 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..5763f5b2f 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, @@ -69,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); @@ -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; }; @@ -111,8 +125,7 @@ class GenericSymbolRef final { public: enum class Kind { ClassOrModule, - UndeclaredField, - DeclaredField, + Field, Method, }; @@ -124,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; @@ -161,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) { @@ -179,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; @@ -192,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); } } @@ -206,16 +207,17 @@ 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; } +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: - case Kind::DeclaredField: + case Kind::Field: return false; case Kind::ClassOrModule: case Kind::Method: @@ -231,4 +233,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.rb b/test/scip/testdata/field_inheritance.rb index c218bd4a1..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 @@ -134,3 +146,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 8e5959812..4703f3ff9 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,12 @@ 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`. +# relation definition=[..] C1#`@i`. return end end @@ -148,10 +154,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 +172,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 @@ -210,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 @@ -254,3 +290,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 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 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 diff --git a/test/scip/testdata/hoverdocs.snapshot.rb b/test/scip/testdata/hoverdocs.snapshot.rb index ccea53e4f..f0b01bcef 100644 --- a/test/scip/testdata/hoverdocs.snapshot.rb +++ b/test/scip/testdata/hoverdocs.snapshot.rb @@ -297,10 +297,6 @@ def p1 # | ``` @@y = 10 # ^^^ definition [..] ``#`@@y`. -# documentation -# | ```ruby -# | @@y (T.untyped) -# | ``` # ^^^^^^^^ reference [..] ``#`@@y`. # override_documentation # | ```ruby @@ -320,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 @@ -369,25 +361,24 @@ def p1 # documentation # | overrides K1's p1 @x = 20 -# ^^ definition [..] K1#`@x`. -# documentation -# | ```ruby -# | @x (T.untyped) -# | ``` +# ^^ definition [..] K2#`@x`. +# relation definition=[..] K1#`@x`. @@y = 20 -# ^^^ definition [..] ``#`@@y`. +# ^^^ definition [..] ``#`@@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)) # | ``` +# 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 new file mode 100644 index 000000000..98f96636d --- /dev/null +++ b/test/scip/testdata/mixin.rb @@ -0,0 +1,473 @@ +# 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 + +# 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 T4 + 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 T5 + 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 T6 + 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 T7 + 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 T8 + 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 T9 + 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 T10 + 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 T11 + 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 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 + 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 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 + 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 T14 + 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 T15 + 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 T16 + 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 T17 + 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 W3 + 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 W4 + 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 W5 + 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 \ No newline at end of file diff --git a/test/scip/testdata/mixin.snapshot.rb b/test/scip/testdata/mixin.snapshot.rb new file mode 100644 index 000000000..dac321db9 --- /dev/null +++ b/test/scip/testdata/mixin.snapshot.rb @@ -0,0 +1,916 @@ + # typed: true + + module M +# ^ definition [..] M# + def f; puts 'M.f'; end +# ^ definition [..] M#f(). + end + + class C1 +# ^^ definition [..] C1# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] M# + def f; puts 'C1.f'; end +# ^ definition [..] C1#f(). +# ^^^^ reference [..] Kernel#puts(). + 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; puts 'D1.f'; end +# ^ definition [..] D1#f(). +# ^^^^ reference [..] Kernel#puts(). + 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(). + + # 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#`@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#`@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 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_2; @f = 2; end +# ^^^^^^^ definition [..] T4#C0#set_f_2(). +# ^^ definition [..] T4#C0#`@f`. +# ^^^^^^ reference [..] T4#C0#`@f`. + 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`. reference=[..] T4#M#`@f`. + end + end + + # Definition in transitively included module & superclass & Self + + 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_2; @f = 2; end +# ^^^^^^^ definition [..] T5#C0#set_f_2(). +# ^^ definition [..] T5#C0#`@f`. +# ^^^^^^ reference [..] T5#C0#`@f`. + 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`. +# relation definition=[..] T5#C0#`@f`. + end + end + + # Definition in directly included module & superclass only + + 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# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T6#C0#set_f_1(). +# ^^ definition [..] T6#C0#`@f`. +# ^^^^^^ reference [..] T6#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] T6#C1# +# ^^ definition [..] T6#C0# + def get_f; @f; end +# ^^^^^ definition [..] T6#C1#get_f(). +# ^^ reference [..] T6#C1#`@f`. +# relation definition=[..] T6#C0#`@f`. + end + end + + # Definition in transitively included module & superclass only + + module T7 +# ^^ definition [..] T7# + module M0 +# ^^ definition [..] T7#M0# + def set_f_0; @f = 0; end +# ^^^^^^^ 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# + def set_f_1; @f = 1; end +# ^^^^^^^ definition [..] T7#C0#set_f_1(). +# ^^ definition [..] T7#C0#`@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 & 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# + 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 +# ^^ definition [..] T8#C1# +# ^^ definition [..] T8#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 & 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# + 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 +# ^^ definition [..] T9#C1# +# ^^ definition [..] T9#C0# + def get_f; @f; end +# ^^^^^ definition [..] T9#C1#get_f(). +# ^^ reference [..] T9#C1#`@f`. +# relation definition=[..] T9#C0#`@f`. + end + end + + # Definition in module included via superclass & Self + + module T10 +# ^^^ definition [..] T10# + module M +# ^ definition [..] T10#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T10#M#set_f_0(). +# ^^ definition [..] T10#M#`@f`. +# ^^^^^^ reference [..] T10#M#`@f`. + end + + class C0 +# ^^ definition [..] T10#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T10#M# + end + + 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#C1#get_f(). +# ^^ reference [..] T10#C1#`@f`. + end + end + + # Definition in module included via superclass only + + module T11 +# ^^^ definition [..] T11# + module M +# ^ definition [..] T11#M# + def set_f_0; @f = 0; end +# ^^^^^^^ definition [..] T11#M#set_f_0(). +# ^^ definition [..] T11#M#`@f`. +# ^^^^^^ reference [..] T11#M#`@f`. + end + + class C0 +# ^^ definition [..] T11#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] T11#M# + end + + class C1 < C0 +# ^^ definition [..] T11#C1# +# ^^ definition [..] T11#C0# + def get_f; @f; end +# ^^^^^ definition [..] T11#C1#get_f(). +# ^^ reference [..] T11#C1#`@f`. + end + end + + # Definition in multiple transitively included modules & common child & 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# + 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 +# ^ 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#M2#`@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 & common child 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# + 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 +# ^ 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 transitively 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 + + module M2 +# ^^ definition [..] T14#M2# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T14#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] T14#M1# + 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`. + def get_f; @f; end +# ^^^^^ definition [..] T14#C#get_f(). +# ^^ reference [..] T14#C#`@f`. + end + end + + # Definition in multiple transitively 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 + + 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. + + # 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(). +# ^^ 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 W3 +# ^^ definition [..] W3# + module M +# ^ definition [..] W3#M# + def get_f; @f; end +# ^^^^^ definition [..] W3#M#get_f(). +# ^^ reference [..] W3#M#`@f`. + end + + class C0 +# ^^ definition [..] W3#C0# + def set_f; @f = 0; end +# ^^^^^ definition [..] W3#C0#set_f(). +# ^^ definition [..] W3#C0#`@f`. +# ^^^^^^ reference [..] W3#C0#`@f`. + end + + class C1 < C0 +# ^^ definition [..] W3#C1# +# ^^ definition [..] W3#C0# + include M +# ^^^^^^^ reference [..] Module#include(). +# ^ reference [..] W3#M# + def get_fp1; @f + 1; end +# ^^^^^^^ 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 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# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W4#M0# + def set_f; @f = 0; end +# ^^^^^ definition [..] W4#M1#set_f(). +# ^^ definition [..] W4#M1#`@f`. +# relation reference=[..] W4#M0#`@f`. +# ^^^^^^ reference [..] W4#M1#`@f`. + end + + class C +# ^ definition [..] W4#C# + 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 + + # Reference in one directly included module with def in other directly included module + + module W5 +# ^^ definition [..] W5# + module M0 +# ^^ definition [..] W5#M0# + def get_f; @f; end +# ^^^^^ definition [..] W5#M0#get_f(). +# ^^ reference [..] W5#M0#`@f`. + end + + module M1 +# ^^ definition [..] W5#M1# + def set_f; @f + 1; end +# ^^^^^ definition [..] W5#M1#set_f(). +# ^^ reference [..] W5#M1#`@f`. + end + + class C +# ^ definition [..] W5#C# + include M0 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W5#M0# + include M1 +# ^^^^^^^ reference [..] Module#include(). +# ^^ reference [..] W5#M1# + def get_fp1; @f + 1; end +# ^^^^^^^ definition [..] W5#C#get_fp1(). +# ^^ reference [..] W5#C#`@f`. + end + end diff --git a/test/scip/testdata/type_change.snapshot.rb b/test/scip/testdata/type_change.snapshot.rb index 3db241301..f498fec32 100644 --- a/test/scip/testdata/type_change.snapshot.rb +++ b/test/scip/testdata/type_change.snapshot.rb @@ -151,22 +151,10 @@ def change_type(b) # | ``` @f = nil # ^^ definition [..] C#`@f`. -# documentation -# | ```ruby -# | @f (T.untyped) -# | ``` @@g = nil # ^^^ definition [..] ``#`@@g`. -# documentation -# | ```ruby -# | @@g (T.untyped) -# | ``` @k = nil # ^^ definition [..] C#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` if b @f = 1 # ^^ reference (write) [..] C#`@f`. @@ -246,52 +234,52 @@ 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 [..] ``#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` +# relation definition=[..] ``#`@@g`. @k = 1 -# ^^ definition [..] C#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` -# ^^^^^^ reference [..] C#`@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 [..] C#`@f`. +# ^^ definition [..] D#`@f`. # documentation # | ```ruby # | @f (T.untyped) # | ``` +# relation definition=[..] C#`@f`. @@g = 'g' -# ^^^ definition [..] ``#`@@g`. +# ^^^ definition [..] ``#`@@g`. # documentation # | ```ruby # | @@g (T.untyped) # | ``` +# relation definition=[..] ``#`@@g`. @k = 'k' -# ^^ definition [..] C#`@k`. -# documentation -# | ```ruby -# | @k (T.untyped) -# | ``` -# ^^^^^^^^ reference [..] C#`@k`. +# ^^ definition [..] D#`@k`. +# relation definition=[..] C#`@k`. +# ^^^^^^^^ reference [..] D#`@k`. # override_documentation # | ```ruby # | @k (String("k")) # | ``` +# relation definition=[..] C#`@k`. end end end diff --git a/test/scip_test_runner.cc b/test/scip_test_runner.cc index 26cec1189..bb46ad2cd 100644 --- a/test/scip_test_runner.cc +++ b/test/scip_test_runner.cc @@ -219,10 +219,15 @@ 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 = + absl::c_any_of(symbolInfo.relationships(), [](const auto &rel) -> bool { return rel.is_definition(); }); + if (!isDefinition && !isDefinedByAnother) { + continue; + } printDocs(symbolInfo.documentation(), "documentation"); relationships.clear(); @@ -233,16 +238,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'; }