33
33
#include " sorbet_version/sorbet_version.h"
34
34
35
35
#include " scip_indexer/Debug.h"
36
+ #include " scip_indexer/SCIPFieldResolve.h"
36
37
#include " scip_indexer/SCIPSymbolRef.h"
37
38
#include " scip_indexer/SCIPUtils.h"
38
39
@@ -173,6 +174,11 @@ class SCIPState {
173
174
UnorderedSet<pair<core::FileRef, string>> emittedSymbols;
174
175
UnorderedMap<core::FileRef, vector<scip::SymbolInformation>> symbolMap;
175
176
177
+ // Stores the relationships that apply to a field or a method.
178
+ RelationshipsMap relationshipsMap;
179
+
180
+ FieldResolver fieldResolver;
181
+
176
182
vector<scip::Document> documents;
177
183
vector<scip::SymbolInformation> externalSymbols;
178
184
@@ -222,7 +228,8 @@ class SCIPState {
222
228
}
223
229
224
230
private:
225
- Emitted saveSymbolInfo (core::FileRef file, const string &symbolString, const SmallVec<string> &docs) {
231
+ Emitted saveSymbolInfo (core::FileRef file, const string &symbolString, const SmallVec<string> &docs,
232
+ const SmallVec<scip::Relationship> &rels) {
226
233
if (this ->emittedSymbols .contains ({file, symbolString})) {
227
234
return Emitted::Earlier;
228
235
}
@@ -231,15 +238,19 @@ class SCIPState {
231
238
for (auto &doc : docs) {
232
239
symbolInfo.add_documentation (doc);
233
240
}
241
+ for (auto &rel : rels) {
242
+ *symbolInfo.add_relationships () = rel;
243
+ }
234
244
this ->symbolMap [file].push_back (symbolInfo);
235
245
return Emitted::Now;
236
246
}
237
247
238
248
absl::Status saveDefinitionImpl (const core::GlobalState &gs, core::FileRef file, const string &symbolString,
239
- core::Loc occLoc, const SmallVec<string> &docs) {
249
+ core::Loc occLoc, const SmallVec<string> &docs,
250
+ const SmallVec<scip::Relationship> &rels) {
240
251
ENFORCE (!symbolString.empty ());
241
252
242
- auto emitted = this ->saveSymbolInfo (file, symbolString, docs);
253
+ auto emitted = this ->saveSymbolInfo (file, symbolString, docs, rels );
243
254
244
255
occLoc = trimColonColonPrefix (gs, occLoc);
245
256
scip::Occurrence occurrence;
@@ -336,7 +347,7 @@ class SCIPState {
336
347
ENFORCE (var.has_value (), " Failed to find source text for definition of local variable" );
337
348
docStrings.push_back (fmt::format (" ```ruby\n {} ({})\n ```" , var.value (), type.show (gs)));
338
349
}
339
- return this ->saveDefinitionImpl (gs, file, occ.toSCIPString (gs, file), loc, docStrings);
350
+ return this ->saveDefinitionImpl (gs, file, occ.toSCIPString (gs, file), loc, docStrings, {} );
340
351
}
341
352
342
353
// Save definition when you have a sorbet Symbol.
@@ -349,19 +360,29 @@ class SCIPState {
349
360
350
361
auto occLoc = loc.has_value () ? core::Loc (file, loc.value ()) : symRef.symbolLoc (gs);
351
362
scip::Symbol symbol;
352
- auto status = symRef.withoutType ().symbolForExpr (gs, this ->gemMetadata , occLoc, symbol);
363
+ auto untypedSymRef = symRef.withoutType ();
364
+ auto status = untypedSymRef.symbolForExpr (gs, this ->gemMetadata , occLoc, symbol);
353
365
if (!status.ok ()) {
354
366
return status;
355
367
}
356
- absl::StatusOr<const string *> valueOrStatus (this ->saveSymbolString (gs, symRef. withoutType () , &symbol));
368
+ absl::StatusOr<const string *> valueOrStatus (this ->saveSymbolString (gs, untypedSymRef , &symbol));
357
369
if (!valueOrStatus.ok ()) {
358
370
return valueOrStatus.status ();
359
371
}
360
372
const string &symbolString = *valueOrStatus.value ();
361
373
362
374
SmallVec<string> docs;
363
375
symRef.saveDocStrings (gs, symRef.definitionType (), occLoc, docs);
364
- return this ->saveDefinitionImpl (gs, file, symbolString, occLoc, docs);
376
+
377
+ SmallVec<scip::Relationship> rels;
378
+ untypedSymRef.saveRelationships (this ->relationshipsMap , rels,
379
+ [this , &gs](UntypedGenericSymbolRef sym, std::string &out) {
380
+ auto status = this ->saveSymbolString (gs, sym, nullptr );
381
+ ENFORCE (status.ok ());
382
+ out = *status.value ();
383
+ });
384
+
385
+ return this ->saveDefinitionImpl (gs, file, symbolString, occLoc, docs, rels);
365
386
}
366
387
367
388
absl::Status saveReference (const core::GlobalState &gs, core::FileRef file, OwnedLocal occ,
@@ -457,63 +478,6 @@ string format_ancestry(const core::GlobalState &gs, core::SymbolRef sym) {
457
478
return out.str ();
458
479
}
459
480
460
- static absl::variant</* owner*/ core::ClassOrModuleRef, core::SymbolRef>
461
- findUnresolvedFieldTransitive (const core::GlobalState &gs, core::Loc loc, core::ClassOrModuleRef start,
462
- core::NameRef field) {
463
- auto fieldText = field.shortName (gs);
464
- auto isInstanceVar = fieldText.size () >= 2 && fieldText[0 ] == ' @' && fieldText[1 ] != ' @' ;
465
- auto isClassInstanceVar = isInstanceVar && start.data (gs)->isSingletonClass (gs);
466
- // Class instance variables are not inherited, unlike ordinary instance
467
- // variables or class variables.
468
- if (isClassInstanceVar) {
469
- return start;
470
- }
471
- auto isClassVar = fieldText.size () >= 2 && fieldText[0 ] == ' @' && fieldText[1 ] == ' @' ;
472
- if (isClassVar && !start.data (gs)->isSingletonClass (gs)) {
473
- // Triggered when undeclared class variables are accessed from instance methods.
474
- start = start.data (gs)->lookupSingletonClass (gs);
475
- }
476
-
477
- // TODO(varun): Should we add a cache here? It seems wasteful to redo
478
- // work for every occurrence.
479
- if (gs.unresolvedFields .find (start) == gs.unresolvedFields .end () ||
480
- !gs.unresolvedFields .find (start)->second .contains (field)) {
481
- // Triggered by code patterns like:
482
- // # top-level
483
- // def MyClass.method
484
- // # blah
485
- // end
486
- // which is not supported by Sorbet.
487
- LOG_DEBUG (gs, loc,
488
- fmt::format (" couldn't find field {} in class {};\n "
489
- " are you using a code pattern like def MyClass.method which is unsupported by Sorbet?" ,
490
- field.exists () ? field.toString (gs) : " <non-existent>" ,
491
- start.exists () ? start.showFullName (gs) : " <non-existent>" ));
492
- // As a best-effort guess, assume that the definition is
493
- // in this class but we somehow missed it.
494
- return start;
495
- }
496
-
497
- auto best = start;
498
- auto cur = start;
499
- while (cur.exists ()) {
500
- auto klass = cur.data (gs);
501
- auto sym = klass->findMember (gs, field);
502
- if (sym.exists ()) {
503
- return sym;
504
- }
505
- auto it = gs.unresolvedFields .find (cur);
506
- if (it != gs.unresolvedFields .end () && it->second .contains (field)) {
507
- best = cur;
508
- }
509
- if (cur == klass->superClass ()) { // FIXME(varun): Handle mix-ins
510
- break ;
511
- }
512
- cur = klass->superClass ();
513
- }
514
- return best;
515
- }
516
-
517
481
// Loosely inspired by AliasesAndKeywords in IREmitterContext.cc
518
482
class AliasMap final {
519
483
public:
@@ -528,7 +492,8 @@ class AliasMap final {
528
492
public:
529
493
AliasMap () = default ;
530
494
531
- void populate (const core::Context &ctx, const cfg::CFG &cfg) {
495
+ void populate (const core::Context &ctx, const cfg::CFG &cfg, FieldResolver &fieldResolver,
496
+ RelationshipsMap &relMap) {
532
497
this ->map = {};
533
498
auto &gs = ctx.state ;
534
499
auto method = ctx.owner ;
@@ -553,25 +518,40 @@ class AliasMap final {
553
518
if (sym == core::Symbols::Magic_undeclaredFieldStub ()) {
554
519
ENFORCE (!bind.loc .empty ());
555
520
ENFORCE (klass.isClassOrModule ());
556
- auto result = findUnresolvedFieldTransitive (ctx, ctx.locAt (bind.loc ), klass.asClassOrModuleRef (),
557
- instr->name );
558
- if (absl::holds_alternative<core::ClassOrModuleRef>(result)) {
559
- auto klass = absl::get<core::ClassOrModuleRef>(result);
560
- if (klass.exists ()) {
561
- this ->map .insert ( // no trim(...) because undeclared fields shouldn't have ::
562
- {bind.bind .variable ,
563
- {GenericSymbolRef::undeclaredField (klass, instr->name , bind.bind .type ), bind.loc ,
564
- false }});
521
+ auto [result, cacheHit] = fieldResolver.findUnresolvedFieldTransitive (
522
+ ctx, ctx.locAt (bind.loc ), {klass.asClassOrModuleRef (), instr->name });
523
+ auto checkExists = [&](bool exists, const std::string &text) {
524
+ ENFORCE (exists,
525
+ " Returned non-existent {} from findUnresolvedFieldTransitive with start={}, "
526
+ " field={}, file={}, loc={}" ,
527
+ text, klass.exists () ? klass.toStringFullName (gs) : " <non-existent>" ,
528
+ instr->name .exists () ? instr->name .toString (gs) : " <non-existent>" ,
529
+ ctx.file .data (gs).path (), ctx.locAt (bind.loc ).showRawLineColumn (gs));
530
+ };
531
+ switch (result.inherited .kind ()) {
532
+ case FieldQueryResult::Kind::FromUndeclared: {
533
+ auto klass = result.inherited .originalClass ();
534
+ checkExists (klass.exists (), " class" );
535
+ auto namedSymRef = GenericSymbolRef::undeclaredField (klass, instr->name , bind.bind .type );
536
+ if (!cacheHit) {
537
+ // It may be the case that the mixin values are already stored because of the
538
+ // traversal in some other function. In that case, don't bother overriding.
539
+ relMap.insert ({namedSymRef.withoutType (), result.mixedIn });
540
+ }
541
+ // no trim(...) because undeclared fields shouldn't have ::
542
+ this ->map .insert ({bind.bind .variable , {namedSymRef, bind.loc , false }});
543
+ break ;
565
544
}
566
- } else if (absl::holds_alternative<core::SymbolRef>(result)) {
567
- auto fieldSym = absl::get<core::SymbolRef>(result);
568
- if (fieldSym.exists ()) {
569
- this ->map .insert (
570
- {bind.bind .variable ,
571
- {GenericSymbolRef::declaredField (fieldSym, bind.bind .type ), trim (bind.loc ), false }});
545
+ case FieldQueryResult::Kind::FromDeclared: {
546
+ auto fieldSym = result.inherited .originalSymbol ();
547
+ checkExists (fieldSym.exists (), " field" );
548
+ auto namedSymRef = GenericSymbolRef::declaredField (fieldSym, bind.bind .type );
549
+ if (!cacheHit) {
550
+ relMap.insert ({namedSymRef.withoutType (), result.mixedIn });
551
+ }
552
+ this ->map .insert ({bind.bind .variable , {namedSymRef, trim (bind.loc ), false }});
553
+ break ;
572
554
}
573
- } else {
574
- ENFORCE (false , " Should've handled all cases of variant earlier" );
575
555
}
576
556
continue ;
577
557
}
@@ -817,7 +797,9 @@ class CFGTraversal final {
817
797
818
798
public:
819
799
void traverse (const cfg::CFG &cfg) {
820
- this ->aliasMap .populate (this ->ctx , cfg);
800
+ this ->aliasMap .populate (this ->ctx , cfg, this ->scipState .fieldResolver , this ->scipState .relationshipsMap );
801
+ fmt::print (stderr, " log: [traverse] relationshipsMap = {}\n " ,
802
+ showRawRelationshipsMap (this ->ctx .state , this ->scipState .relationshipsMap ));
821
803
auto &gs = this ->ctx .state ;
822
804
auto file = this ->ctx .file ;
823
805
auto method = this ->ctx .owner ;
0 commit comments