1
+ #include < vector>
2
+
3
+ #include " absl/strings/ascii.h"
4
+
5
+ #include " common/common.h"
6
+ #include " core/GlobalState.h"
7
+ #include " core/SymbolRef.h"
8
+ #include " core/Symbols.h"
9
+
10
+ #include " scip_indexer/Debug.h"
11
+ #include " scip_indexer/SCIPFieldResolve.h"
12
+
13
+ using namespace std ;
14
+
15
+ namespace sorbet ::scip_indexer {
16
+
17
+ string FieldQueryResult::Data::showRaw (const core::GlobalState &gs) const {
18
+ switch (this ->kind ()) {
19
+ case Kind::FromDeclared:
20
+ return this ->originalSymbol ().showRaw (gs);
21
+ case Kind::FromUndeclared:
22
+ return fmt::format (" In({})" , absl::StripAsciiWhitespace (this ->originalClass ().showFullName (gs)));
23
+ }
24
+ }
25
+
26
+ string FieldQueryResult::showRaw (const core::GlobalState &gs) const {
27
+ if (this ->mixedIn ->empty ()) {
28
+ return fmt::format (" FieldQueryResult(inherited: {})" , this ->inherited .showRaw (gs));
29
+ }
30
+ return fmt::format (" FieldQueryResult(inherited: {}, mixins: {})" , this ->inherited .showRaw (gs),
31
+ showVec (*this ->mixedIn .get (), [&gs](const auto &mixin) -> string { return mixin.showRaw (gs); }));
32
+ }
33
+
34
+ void FieldResolver::resetMixins () {
35
+ this ->mixinQueue .clear ();
36
+ }
37
+
38
+ // Compute all transitively included modules which mention the field being queried.
39
+ //
40
+ // If an include chain for a field looks like class C.@f <- module M2.@f <- module M1.@f,
41
+ // both M1 and M2 will be included in the results (this avoids any kind of postprocessing
42
+ // of a transitive closure of relationships at the cost of a larger index).
43
+ void FieldResolver::findUnresolvedFieldInMixinsTransitive (const core::GlobalState &gs, FieldQuery query,
44
+ vector<FieldQueryResult::Data> &out) {
45
+ this ->mixinQueue .clear ();
46
+ for (auto mixin : query.start .data (gs)->mixins ()) {
47
+ this ->mixinQueue .push_back (mixin);
48
+ }
49
+ auto field = query.field ;
50
+ using Data = FieldQueryResult::Data;
51
+ while (auto m = this ->mixinQueue .try_pop_front ()) {
52
+ auto mixin = m.value ();
53
+ auto sym = mixin.data (gs)->findMember (gs, field);
54
+ if (sym.exists ()) {
55
+ out.push_back (Data (sym));
56
+ continue ;
57
+ }
58
+ auto it = gs.unresolvedFields .find (mixin);
59
+ if (it != gs.unresolvedFields .end () && it->second .contains (field)) {
60
+ out.push_back (Data (mixin));
61
+ }
62
+ }
63
+ }
64
+
65
+ FieldQueryResult::Data FieldResolver::findUnresolvedFieldInInheritanceChain (const core::GlobalState &gs, core::Loc loc,
66
+ FieldQuery query) {
67
+ auto start = query.start ;
68
+ auto field = query.field ;
69
+
70
+ auto fieldText = query.field .shortName (gs);
71
+ auto isInstanceVar = fieldText.size () >= 2 && fieldText[0 ] == ' @' && fieldText[1 ] != ' @' ;
72
+ auto isClassInstanceVar = isInstanceVar && start.data (gs)->isSingletonClass (gs);
73
+ // Class instance variables are not inherited, unlike ordinary instance
74
+ // variables or class variables.
75
+ if (isClassInstanceVar) {
76
+ return FieldQueryResult::Data (start);
77
+ }
78
+ auto isClassVar = fieldText.size () >= 2 && fieldText[0 ] == ' @' && fieldText[1 ] == ' @' ;
79
+ if (isClassVar && !start.data (gs)->isSingletonClass (gs)) {
80
+ // Triggered when undeclared class variables are accessed from instance methods.
81
+ start = start.data (gs)->lookupSingletonClass (gs);
82
+ }
83
+
84
+ if (gs.unresolvedFields .find (start) == gs.unresolvedFields .end () ||
85
+ !gs.unresolvedFields .find (start)->second .contains (field)) {
86
+ // Triggered by code patterns like:
87
+ // # top-level
88
+ // def MyClass.method
89
+ // # blah
90
+ // end
91
+ // which is not supported by Sorbet.
92
+ LOG_DEBUG (gs, loc,
93
+ fmt::format (" couldn't find field {} in class {};\n "
94
+ " are you using a code pattern like def MyClass.method which is unsupported by Sorbet?" ,
95
+ field.exists () ? field.toString (gs) : " <non-existent>" ,
96
+ start.exists () ? start.showFullName (gs) : " <non-existent>" ));
97
+ // As a best-effort guess, assume that the definition is
98
+ // in this class but we somehow missed it.
99
+ return FieldQueryResult::Data (start);
100
+ }
101
+
102
+ auto best = start;
103
+ auto cur = start;
104
+ while (cur.exists ()) {
105
+ auto klass = cur.data (gs);
106
+ auto sym = klass->findMember (gs, field);
107
+ if (sym.exists ()) { // TODO(varun): Is this early exit justified?
108
+ // Maybe it is possible to hit this in multiple ancestors?
109
+ return FieldQueryResult::Data (sym);
110
+ }
111
+ auto it = gs.unresolvedFields .find (cur);
112
+ if (it != gs.unresolvedFields .end () && it->second .contains (field)) {
113
+ best = cur;
114
+ }
115
+
116
+ if (cur == klass->superClass ()) { // FIXME(varun): Handle mix-ins
117
+ break ;
118
+ }
119
+ cur = klass->superClass ();
120
+ }
121
+ return FieldQueryResult::Data (best);
122
+ }
123
+
124
+ pair<FieldQueryResult, bool > FieldResolver::findUnresolvedFieldTransitive (const core::GlobalState &gs, core::Loc loc,
125
+ FieldQuery query) {
126
+ auto cacheIt = this ->cache .find (query);
127
+ if (cacheIt != this ->cache .end ()) {
128
+ return {cacheIt->second , true };
129
+ }
130
+ auto inherited = this ->findUnresolvedFieldInInheritanceChain (gs, loc, query);
131
+ using Data = FieldQueryResult::Data;
132
+ vector<Data> mixins;
133
+ findUnresolvedFieldInMixinsTransitive (gs, query, mixins);
134
+ auto [it, inserted] =
135
+ this ->cache .insert ({query, FieldQueryResult{inherited, make_shared<vector<Data>>(move (mixins))}});
136
+ ENFORCE (inserted);
137
+ return {it->second , false };
138
+ }
139
+
140
+ } // namespace sorbet::scip_indexer
0 commit comments