|
24 | 24 | #include "cfg/CFG.h"
|
25 | 25 | #include "common/common.h"
|
26 | 26 | #include "common/sort.h"
|
| 27 | +#include "core/Error.h" |
27 | 28 | #include "core/ErrorQueue.h"
|
28 | 29 | #include "core/Loc.h"
|
29 | 30 | #include "core/SymbolRef.h"
|
@@ -57,6 +58,29 @@ static uint32_t fnv1a_32(const string &s) {
|
57 | 58 |
|
58 | 59 | namespace sorbet::scip_indexer {
|
59 | 60 |
|
| 61 | +constexpr sorbet::core::ErrorClass SCIPRubyDebug{400, sorbet::core::StrictLevel::False}; |
| 62 | + |
| 63 | +void _log_debug(const sorbet::core::GlobalState &gs, sorbet::core::Loc loc, std::string s) { |
| 64 | + if (auto e = gs.beginError(loc, SCIPRubyDebug)) { |
| 65 | + auto lines = absl::StrSplit(s, '\n'); |
| 66 | + for (auto line = lines.begin(); line != lines.end(); line++) { |
| 67 | + auto text = string(line->begin(), line->length()); |
| 68 | + if (line == lines.begin()) { |
| 69 | + e.setHeader("[scip-ruby] {}", text); |
| 70 | + } else { |
| 71 | + e.addErrorNote("{}", text); |
| 72 | + } |
| 73 | + } |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +#ifndef NDEBUG |
| 78 | +#define LOG_DEBUG(__gs, __loc, __s) _log_debug(__gs, __loc, __s) |
| 79 | +#else |
| 80 | +#define LOG_DEBUG(__gs, __s) \ |
| 81 | + {} |
| 82 | +#endif |
| 83 | + |
60 | 84 | // TODO(varun): This is an inline workaround for https://github.com/sorbet/sorbet/issues/5925
|
61 | 85 | // I've not changed the main definition because I didn't bother to rerun the tests with the change.
|
62 | 86 | static bool isTemporary(const core::GlobalState &gs, const core::LocalVariable &var) {
|
@@ -779,31 +803,46 @@ string format_ancestry(const core::GlobalState &gs, core::SymbolRef sym) {
|
779 | 803 | }
|
780 | 804 |
|
781 | 805 | static absl::variant</*owner*/ core::ClassOrModuleRef, core::SymbolRef>
|
782 |
| -findUnresolvedFieldTransitive(const core::GlobalState &gs, core::ClassOrModuleRef start, core::NameRef field) { |
| 806 | +findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, core::ClassOrModuleRef start, |
| 807 | + core::NameRef field) { |
783 | 808 | // TODO(varun): Should we add a cache here? It seems wasteful to redo
|
784 | 809 | // work for every occurrence.
|
785 | 810 | if (gs.unresolvedFields.find(start) == gs.unresolvedFields.end()) {
|
786 |
| - fmt::print(stderr, "log: [findUFT] start = {}, field = {}\n", start.showFullName(gs), field.toString(gs)); |
| 811 | + // Triggered by code patterns like: |
| 812 | + // # top-level |
| 813 | + // def MyClass.method |
| 814 | + // # blah |
| 815 | + // end |
| 816 | + // which is not supported by Sorbet. |
| 817 | + LOG_DEBUG(gs, loc, |
| 818 | + fmt::format("couldn't find field {} in class {};\n" |
| 819 | + "are you using a code pattern like def MyClass.method which is unsupported by Sorbet?", |
| 820 | + field.exists() ? field.toString(gs) : "<non-existent>", |
| 821 | + start.exists() ? start.showFullName(gs) : "<non-existent>")); |
| 822 | + return core::ClassOrModuleRef(); |
787 | 823 | }
|
788 |
| - ENFORCE(gs.unresolvedFields.find(start) != gs.unresolvedFields.end()); |
789 |
| - ENFORCE(gs.unresolvedFields.find(start)->second.contains(field)); |
| 824 | + ENFORCE(gs.unresolvedFields.find(start)->second.contains(field), "loc = {}, start = {}, field = {}\n", |
| 825 | + loc.showRawLineColumn(gs), start.showFullName(gs), field.toString(gs)); |
790 | 826 | auto best = start;
|
791 |
| - // auto cur = start; |
792 |
| - // while (cur.exists()) { |
793 |
| - // auto klass = cur.data(gs); |
794 |
| - // auto sym = klass->findMember(gs, field); |
795 |
| - // if (sym.exists()) { |
796 |
| - // return sym; |
797 |
| - // } |
798 |
| - // auto it = gs.unresolvedFields.find(cur); |
799 |
| - // if (it != gs.unresolvedFields.end() && it->second.contains(field)) { |
800 |
| - // best = cur; |
801 |
| - // } |
802 |
| - // if (cur == klass->superClass()) { // TODO(varun): Handle mix-ins here somehow? |
803 |
| - // break; |
804 |
| - // } |
805 |
| - // cur = klass->superClass(); |
806 |
| - // } |
| 827 | + auto cur = start; |
| 828 | + while (cur.exists()) { |
| 829 | + fmt::print(stderr, "log: [findUFT] cur = {}, field = {}\n", cur.showFullName(gs), field.toString(gs)); |
| 830 | + auto klass = cur.data(gs); |
| 831 | + auto sym = klass->findMember(gs, field); |
| 832 | + if (sym.exists()) { |
| 833 | + return sym; |
| 834 | + } |
| 835 | + auto it = gs.unresolvedFields.find(cur); |
| 836 | + if (it != gs.unresolvedFields.end() && it->second.contains(field)) { |
| 837 | + best = cur; |
| 838 | + fmt::print(stderr, "log: [findUFT] updated best = {}, start = {}, field = {}\n", best.showFullName(gs), |
| 839 | + start.showFullName(gs), field.toString(gs)); |
| 840 | + } |
| 841 | + if (cur == klass->superClass()) { // TODO(varun): Handle mix-ins here somehow? |
| 842 | + break; |
| 843 | + } |
| 844 | + cur = klass->superClass(); |
| 845 | + } |
807 | 846 | return best;
|
808 | 847 | }
|
809 | 848 |
|
@@ -846,25 +885,30 @@ class AliasMap final {
|
846 | 885 | if (sym == core::Symbols::Magic_undeclaredFieldStub()) {
|
847 | 886 | ENFORCE(!bind.loc.empty());
|
848 | 887 | ENFORCE(klass.isClassOrModule());
|
849 |
| - this->map.insert( // no trim(...) because undeclared fields shouldn't have :: |
850 |
| - {bind.bind.variable, |
851 |
| - {NamedSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc, false}}); |
852 |
| - (void)findUnresolvedFieldTransitive(gs, klass.asClassOrModuleRef(), instr->name); |
853 |
| - // auto result = findUnresolvedFieldTransitive(gs, klass.asClassOrModuleRef(), instr->name); |
854 |
| - // if (absl::holds_alternative<core::ClassOrModuleRef>(result)) { |
855 |
| - // auto klass = absl::get<core::ClassOrModuleRef>(result); |
856 |
| - // this->map.insert( // no trim(...) because undeclared fields shouldn't have :: |
857 |
| - // {bind.bind.variable, |
858 |
| - // {NamedSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc, |
859 |
| - // false}}); |
860 |
| - // } else if (absl::holds_alternative<core::SymbolRef>(result)) { |
861 |
| - // auto fieldSym = absl::get<core::SymbolRef>(result); |
862 |
| - // this->map.insert( |
863 |
| - // {bind.bind.variable, |
864 |
| - // {NamedSymbolRef::declaredField(fieldSym, bind.bind.type), trim(bind.loc), false}}); |
865 |
| - // } else { |
866 |
| - // ENFORCE(false, "Should've handled all cases of variant earlier"); |
867 |
| - // } |
| 888 | + // fmt::print(stderr, "method = {}, klass = {}, enclosingClass = {}, enclosing.singleton = {}\n", |
| 889 | + // method.showFullName(gs), klass.showFullName(gs), |
| 890 | + // method.enclosingClass(gs).showFullName(gs), |
| 891 | + // method.enclosingClass(gs).data(gs)->lookupSingletonClass(gs).showFullName(gs)); |
| 892 | + auto result = findUnresolvedFieldTransitive(ctx, ctx.locAt(bind.loc), klass.asClassOrModuleRef(), |
| 893 | + instr->name); |
| 894 | + if (absl::holds_alternative<core::ClassOrModuleRef>(result)) { |
| 895 | + auto klass = absl::get<core::ClassOrModuleRef>(result); |
| 896 | + if (klass.exists()) { |
| 897 | + this->map.insert( // no trim(...) because undeclared fields shouldn't have :: |
| 898 | + {bind.bind.variable, |
| 899 | + {NamedSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc, |
| 900 | + false}}); |
| 901 | + } |
| 902 | + } else if (absl::holds_alternative<core::SymbolRef>(result)) { |
| 903 | + auto fieldSym = absl::get<core::SymbolRef>(result); |
| 904 | + if (fieldSym.exists()) { |
| 905 | + this->map.insert( |
| 906 | + {bind.bind.variable, |
| 907 | + {NamedSymbolRef::declaredField(fieldSym, bind.bind.type), trim(bind.loc), false}}); |
| 908 | + } |
| 909 | + } else { |
| 910 | + ENFORCE(false, "Should've handled all cases of variant earlier"); |
| 911 | + } |
868 | 912 | continue;
|
869 | 913 | }
|
870 | 914 | if (sym.isFieldOrStaticField()) {
|
@@ -1065,9 +1109,18 @@ class CFGTraversal final {
|
1065 | 1109 | } else {
|
1066 | 1110 | uint32_t localId = this->functionLocals[localRef];
|
1067 | 1111 | auto it = this->localDefinitionType.find(localId);
|
1068 |
| - ENFORCE(it != this->localDefinitionType.end(), "file:{}, code:\n{}\naliasMap: {}\n", file.data(gs).path(), |
1069 |
| - core::Loc(file, loc).toString(gs), this->aliasMap.showRaw(gs, file, cfg)); |
1070 |
| - auto overrideType = computeOverrideType(it->second, type); |
| 1112 | + optional<core::TypePtr> overrideType; |
| 1113 | + if (it != this->localDefinitionType.end()) { |
| 1114 | + overrideType = computeOverrideType(it->second, type); |
| 1115 | + } else { |
| 1116 | + LOG_DEBUG( |
| 1117 | + gs, core::Loc(file, loc), |
| 1118 | + fmt::format( |
| 1119 | + "failed to find type info; are you using a code pattern unsupported by Sorbet?\ndebugging " |
| 1120 | + "information: aliasMap: {}", |
| 1121 | + this->aliasMap.showRaw(gs, file, cfg))); |
| 1122 | + overrideType = type; |
| 1123 | + } |
1071 | 1124 | if (isDefinition) {
|
1072 | 1125 | status = this->scipState.saveDefinition(gs, file, OwnedLocal{this->ctx.owner, localId, loc}, type);
|
1073 | 1126 | } else {
|
@@ -1328,6 +1381,7 @@ namespace sorbet::pipeline::semantic_extension {
|
1328 | 1381 |
|
1329 | 1382 | using LocalSymbolTable = UnorderedMap<core::LocalVariable, core::Loc>;
|
1330 | 1383 |
|
| 1384 | +std::atomic_bool emittedMap = false; |
1331 | 1385 | class SCIPSemanticExtension : public SemanticExtension {
|
1332 | 1386 | public:
|
1333 | 1387 | string indexFilePath;
|
@@ -1438,12 +1492,17 @@ class SCIPSemanticExtension : public SemanticExtension {
|
1438 | 1492 |
|
1439 | 1493 | virtual void typecheck(const core::GlobalState &gs, core::FileRef file, cfg::CFG &cfg,
|
1440 | 1494 | ast::MethodDef &methodDef) const override {
|
1441 |
| - fmt::print(stderr, "unresolvedFields map = {}\n", |
1442 |
| - map_to_string(gs.unresolvedFields, [&gs](core::ClassOrModuleRef klass, auto &set) -> string { |
1443 |
| - return fmt::format( |
1444 |
| - "{}: {}", klass.showFullName(gs), |
1445 |
| - set_to_string(set, [&gs](core::NameRef name) -> string { return name.toString(gs); })); |
1446 |
| - })); |
| 1495 | + auto FALSE = false; |
| 1496 | + if (std::atomic_compare_exchange_strong(&emittedMap, &FALSE, true)) { |
| 1497 | + fmt::print(stderr, "unresolvedFields map = {}\n", |
| 1498 | + map_to_string(gs.unresolvedFields, [&gs](core::ClassOrModuleRef klass, auto &set) -> string { |
| 1499 | + return fmt::format( |
| 1500 | + "{}: {}", klass.showFullName(gs), |
| 1501 | + set_to_string(set, [&gs](core::NameRef name) -> string { return name.toString(gs); })); |
| 1502 | + })); |
| 1503 | + } |
| 1504 | + fmt::print(stderr, "log: cfg = {}\n", cfg.toTextualString(gs, file)); |
| 1505 | + |
1447 | 1506 | if (this->doNothing()) {
|
1448 | 1507 | return;
|
1449 | 1508 | }
|
|
0 commit comments