14
14
#include " absl/status/status.h"
15
15
#include " absl/status/statusor.h"
16
16
#include " absl/strings/str_cat.h"
17
+ #include " absl/strings/str_replace.h"
17
18
#include " absl/strings/str_split.h"
18
19
#include " absl/synchronization/mutex.h"
19
20
#include " spdlog/fmt/fmt.h"
27
28
#include " core/Loc.h"
28
29
#include " core/SymbolRef.h"
29
30
#include " core/Symbols.h"
31
+ #include " main/lsp/lsp.h"
30
32
#include " main/pipeline/semantic_extension/SemanticExtension.h"
31
33
#include " sorbet_version/sorbet_version.h"
32
34
@@ -165,6 +167,7 @@ class GemMetadata final {
165
167
class NamedSymbolRef final {
166
168
core::SymbolRef selfOrOwner;
167
169
core::NameRef name;
170
+ core::TypePtr type;
168
171
169
172
public:
170
173
enum class Kind {
@@ -175,7 +178,7 @@ class NamedSymbolRef final {
175
178
};
176
179
177
180
private:
178
- NamedSymbolRef (core::SymbolRef s, core::NameRef n, Kind k) : selfOrOwner(s), name(n) {
181
+ NamedSymbolRef (core::SymbolRef s, core::NameRef n, core::TypePtr t, Kind k) : selfOrOwner(s), name(n), type(t ) {
179
182
switch (k) {
180
183
case Kind::ClassOrModule:
181
184
ENFORCE (s.isClassOrModule ());
@@ -209,19 +212,19 @@ class NamedSymbolRef final {
209
212
}
210
213
211
214
static NamedSymbolRef classOrModule (core::SymbolRef self) {
212
- return NamedSymbolRef (self, {}, Kind::ClassOrModule);
215
+ return NamedSymbolRef (self, {}, {}, Kind::ClassOrModule);
213
216
}
214
217
215
- static NamedSymbolRef undeclaredField (core::SymbolRef owner, core::NameRef name) {
216
- return NamedSymbolRef (owner, name, Kind::UndeclaredField);
218
+ static NamedSymbolRef undeclaredField (core::SymbolRef owner, core::NameRef name, core::TypePtr type ) {
219
+ return NamedSymbolRef (owner, name, type, Kind::UndeclaredField);
217
220
}
218
221
219
222
static NamedSymbolRef staticField (core::SymbolRef self) {
220
- return NamedSymbolRef (self, {}, Kind::StaticField);
223
+ return NamedSymbolRef (self, {}, {}, Kind::StaticField);
221
224
}
222
225
223
226
static NamedSymbolRef method (core::SymbolRef self) {
224
- return NamedSymbolRef (self, {}, Kind::Method);
227
+ return NamedSymbolRef (self, {}, {}, Kind::Method);
225
228
}
226
229
227
230
Kind kind () const {
@@ -257,8 +260,61 @@ class NamedSymbolRef final {
257
260
return this ->selfOrOwner ;
258
261
}
259
262
263
+ vector<string> docStrings (const core::GlobalState &gs, core::Loc loc) {
264
+ vector<string> docs;
265
+ string markdown = " " ;
266
+ switch (this ->kind ()) {
267
+ case Kind::UndeclaredField: {
268
+ markdown = fmt::format (" {} = T.let(_, {})" , this ->name .show (gs), this ->type .show (gs));
269
+ break ;
270
+ }
271
+ case Kind::StaticField: {
272
+ auto fieldRef = this ->selfOrOwner .asFieldRef ();
273
+ markdown =
274
+ fmt::format (" {} = T.let(_, {})" , fieldRef.showFullName (gs), fieldRef.data (gs)->resultType .show (gs));
275
+ break ;
276
+ }
277
+ case Kind::ClassOrModule: {
278
+ auto ref = this ->selfOrOwner .asClassOrModuleRef ();
279
+ auto classOrModule = ref.data (gs);
280
+ if (classOrModule->isClass ()) {
281
+ auto super = classOrModule->superClass ();
282
+ if (super.exists () && super != core::Symbols::Object ()) {
283
+ markdown = fmt::format (" class {} < {}" , ref.show (gs), super.show (gs));
284
+ } else {
285
+ markdown = fmt::format (" class {}" , ref.show (gs));
286
+ }
287
+ } else {
288
+ markdown = fmt::format (" module {}" , ref.show (gs));
289
+ }
290
+ break ;
291
+ }
292
+ case Kind::Method: {
293
+ auto ref = this ->selfOrOwner .asMethodRef ();
294
+ markdown = realmain::lsp::prettyTypeForMethod (gs, ref, ref.data (gs)->owner .data (gs)->resultType ,
295
+ nullptr , nullptr );
296
+ // FIXME(varun): For some reason, it looks like a bunch of public methods
297
+ // get marked as private here. Avoid printing misleading info until we fix that.
298
+ // https://github.com/sourcegraph/scip-ruby/issues/33
299
+ markdown = absl::StrReplaceAll (markdown, {{" private def" , " def" }, {" ; end" , " " }});
300
+ break ;
301
+ }
302
+ }
303
+ if (!markdown.empty ()) {
304
+ docs.push_back (fmt::format (" ```ruby\n {}\n ```" , markdown));
305
+ }
306
+ auto whatFile = loc.file ();
307
+ if (whatFile.exists ()) {
308
+ if (auto doc = realmain::lsp::findDocumentation (whatFile.data (gs).source (), loc.beginPos ())) {
309
+ docs.push_back (doc.value ());
310
+ }
311
+ }
312
+ return docs;
313
+ }
314
+
260
315
// Returns OK if we were able to compute a symbol for the expression.
261
- absl::Status symbolForExpr (const core::GlobalState &gs, const GemMetadata &metadata, scip::Symbol &symbol) const {
316
+ absl::Status symbolForExpr (const core::GlobalState &gs, const GemMetadata &metadata, scip::Symbol &symbol,
317
+ optional<core::Loc> loc) const {
262
318
// Don't set symbol.scheme and package.manager here because those are hard-coded to 'scip-ruby' and 'gem'
263
319
// anyways.
264
320
scip::Package package;
@@ -364,6 +420,7 @@ core::Loc trimColonColonPrefix(const core::GlobalState &gs, core::Loc baseLoc) {
364
420
class SCIPState {
365
421
string symbolScratchBuffer;
366
422
UnorderedMap<NamedSymbolRef, string> symbolStringCache;
423
+ UnorderedMap<uint32_t , core::TypePtr> localTypes;
367
424
GemMetadata gemMetadata;
368
425
369
426
public:
@@ -408,7 +465,7 @@ class SCIPState {
408
465
status = scip::utils::emitSymbolString (*symbol, this ->symbolScratchBuffer );
409
466
} else {
410
467
scip::Symbol symbol;
411
- status = symRef.symbolForExpr (gs, this ->gemMetadata , symbol);
468
+ status = symRef.symbolForExpr (gs, this ->gemMetadata , symbol, nullopt );
412
469
if (!status.ok ()) {
413
470
return status;
414
471
}
@@ -423,11 +480,14 @@ class SCIPState {
423
480
424
481
private:
425
482
absl::Status saveDefinitionImpl (const core::GlobalState &gs, core::FileRef file, const string &symbolString,
426
- core::Loc occLoc) {
483
+ core::Loc occLoc, const vector<string> &docs ) {
427
484
ENFORCE (!symbolString.empty ());
428
485
occLoc = trimColonColonPrefix (gs, occLoc);
429
486
scip::SymbolInformation symbolInfo;
430
487
symbolInfo.set_symbol (symbolString);
488
+ for (auto &doc : docs) {
489
+ symbolInfo.add_documentation (doc);
490
+ }
431
491
this ->symbolMap [file].push_back (symbolInfo);
432
492
433
493
scip::Occurrence occurrence;
@@ -492,11 +552,18 @@ class SCIPState {
492
552
}
493
553
494
554
public:
495
- absl::Status saveDefinition (const core::GlobalState &gs, core::FileRef file, OwnedLocal occ) {
555
+ absl::Status saveDefinition (const core::GlobalState &gs, core::FileRef file, OwnedLocal occ, core::TypePtr type ) {
496
556
if (this ->cacheOccurrence (gs, file, occ, scip::SymbolRole::Definition)) {
497
557
return absl::OkStatus ();
498
558
}
499
- return this ->saveDefinitionImpl (gs, file, occ.toString (gs, file), core::Loc (file, occ.offsets ));
559
+ vector<string> docStrings;
560
+ auto loc = core::Loc (file, occ.offsets );
561
+ if (type) {
562
+ auto var = loc.source (gs);
563
+ ENFORCE (var.has_value (), " Failed to find source text for definition of local variable" );
564
+ docStrings.push_back (fmt::format (" ```ruby\n {} = T.let(_, {})\n ```" , var.value (), type.show (gs)));
565
+ }
566
+ return this ->saveDefinitionImpl (gs, file, occ.toString (gs, file), loc, docStrings);
500
567
}
501
568
502
569
// Save definition when you have a sorbet Symbol.
@@ -506,7 +573,8 @@ class SCIPState {
506
573
std::optional<core::LocOffsets> loc = std::nullopt) {
507
574
// TODO:(varun) Should we cache here too to avoid emitting duplicate definitions?
508
575
scip::Symbol symbol;
509
- auto status = symRef.symbolForExpr (gs, this ->gemMetadata , symbol);
576
+ auto occLoc = loc.has_value () ? core::Loc (file, loc.value ()) : symRef.symbolLoc (gs);
577
+ auto status = symRef.symbolForExpr (gs, this ->gemMetadata , symbol, occLoc);
510
578
if (!status.ok ()) {
511
579
return status;
512
580
}
@@ -515,10 +583,7 @@ class SCIPState {
515
583
return valueOrStatus.status ();
516
584
}
517
585
string &symbolString = *valueOrStatus.value ();
518
-
519
- auto occLoc = loc.has_value () ? core::Loc (file, loc.value ()) : symRef.symbolLoc (gs);
520
-
521
- return this ->saveDefinitionImpl (gs, file, symbolString, occLoc);
586
+ return this ->saveDefinitionImpl (gs, file, symbolString, occLoc, symRef.docStrings (gs, occLoc));
522
587
}
523
588
524
589
absl::Status saveReference (const core::GlobalState &gs, core::FileRef file, OwnedLocal occ, int32_t symbol_roles) {
@@ -617,7 +682,8 @@ class AliasMap final {
617
682
if (sym == core::Symbols::Magic_undeclaredFieldStub ()) {
618
683
ENFORCE (!bind.loc .empty ());
619
684
this ->map .insert ( // no trim(...) because undeclared fields shouldn't have ::
620
- {bind.bind .variable , {NamedSymbolRef::undeclaredField (klass, instr->name ), bind.loc , false }});
685
+ {bind.bind .variable ,
686
+ {NamedSymbolRef::undeclaredField (klass, instr->name , bind.bind .type ), bind.loc , false }});
621
687
continue ;
622
688
}
623
689
if (sym.isStaticField (gs)) {
@@ -685,6 +751,7 @@ class CFGTraversal final {
685
751
// in the block.
686
752
UnorderedMap<const cfg::BasicBlock *, UnorderedSet<cfg::LocalRef>> blockLocals;
687
753
UnorderedMap<cfg::LocalRef, uint32_t > functionLocals;
754
+ UnorderedMap<uint32_t , core::TypePtr> localTypes;
688
755
AliasMap aliasMap;
689
756
690
757
// Local variable counter that is reset for every function.
@@ -697,9 +764,11 @@ class CFGTraversal final {
697
764
: blockLocals(), functionLocals(), aliasMap(), scipState(scipState), ctx(ctx) {}
698
765
699
766
private:
700
- void addLocal (const cfg::BasicBlock *bb, cfg::LocalRef localRef) {
767
+ uint32_t addLocal (const cfg::BasicBlock *bb, cfg::LocalRef localRef) {
768
+ this ->counter ++;
701
769
this ->blockLocals [bb].insert (localRef);
702
- this ->functionLocals [localRef] = ++this ->counter ;
770
+ this ->functionLocals [localRef] = this ->counter ;
771
+ return this ->counter ;
703
772
}
704
773
705
774
static core::LocOffsets lhsLocIfPresent (const cfg::Binding &binding) {
@@ -717,8 +786,10 @@ class CFGTraversal final {
717
786
// Emit an occurrence for a local variable if applicable.
718
787
//
719
788
// Returns true if an occurrence was emitted.
789
+ //
790
+ // The type should be provided if we have an lvalue.
720
791
bool emitLocalOccurrence (const cfg::CFG &cfg, const cfg::BasicBlock *bb, cfg::LocalOccurrence local,
721
- ValueCategory category) {
792
+ ValueCategory category, core::TypePtr type ) {
722
793
auto localRef = local.variable ;
723
794
auto localVar = localRef.data (cfg);
724
795
auto symRef = this ->aliasMap .try_consume (localRef);
@@ -733,7 +804,9 @@ class CFGTraversal final {
733
804
if (!this ->functionLocals .contains (localRef)) {
734
805
isDefinition = true ; // If we're seeing this for the first time in topological order,
735
806
// The current block must have a definition for the variable.
736
- this ->addLocal (bb, localRef);
807
+ auto id = this ->addLocal (bb, localRef);
808
+ ENFORCE (type, " missing type for lvalue" );
809
+ this ->localTypes [id] = type;
737
810
}
738
811
// The variable wasn't passed in as an argument, and hasn't already been recorded
739
812
// as a local in the block. So this must be a definition line.
@@ -774,9 +847,14 @@ class CFGTraversal final {
774
847
}
775
848
} else {
776
849
uint32_t localId = this ->functionLocals [localRef];
850
+ auto it = this ->localTypes .find (localId);
851
+ core::TypePtr type{};
852
+ if (it != this ->localTypes .end ()) {
853
+ type = it->second ;
854
+ }
777
855
if (isDefinition) {
778
856
status = this ->scipState .saveDefinition (this ->ctx .state , this ->ctx .file ,
779
- OwnedLocal{this ->ctx .owner , localId, loc});
857
+ OwnedLocal{this ->ctx .owner , localId, loc}, type );
780
858
} else {
781
859
status = this ->scipState .saveReference (this ->ctx .state , this ->ctx .file ,
782
860
OwnedLocal{this ->ctx .owner , localId, loc}, referenceRole);
@@ -856,12 +934,12 @@ class CFGTraversal final {
856
934
if (binding.value .tag () != cfg::Tag::Alias && binding.value .tag () != cfg::Tag::ArgPresent) {
857
935
// Emit occurrence information for the LHS
858
936
auto occ = cfg::LocalOccurrence{binding.bind .variable , lhsLocIfPresent (binding)};
859
- this ->emitLocalOccurrence (cfg, bb, occ, ValueCategory::LValue);
937
+ this ->emitLocalOccurrence (cfg, bb, occ, ValueCategory::LValue, binding. bind . type );
860
938
}
861
939
// Emit occurrence information for the RHS
862
940
auto emitLocal = [this , &cfg, &bb, &binding](cfg::LocalRef local) -> void {
863
941
(void )this ->emitLocalOccurrence (cfg, bb, cfg::LocalOccurrence{local, binding.loc },
864
- ValueCategory::RValue);
942
+ ValueCategory::RValue, core::TypePtr () );
865
943
};
866
944
switch (binding.value .tag ()) {
867
945
case cfg::Tag::Literal: {
@@ -879,7 +957,8 @@ class CFGTraversal final {
879
957
880
958
// Emit reference for the receiver, if present.
881
959
if (send->recv .loc .exists () && !send->recv .loc .empty ()) {
882
- this ->emitLocalOccurrence (cfg, bb, send->recv .occurrence (), ValueCategory::RValue);
960
+ this ->emitLocalOccurrence (cfg, bb, send->recv .occurrence (), ValueCategory::RValue,
961
+ core::TypePtr ());
883
962
}
884
963
885
964
// Emit reference for the method being called
@@ -919,7 +998,8 @@ class CFGTraversal final {
919
998
// and the first one is a write. Instead of emitting two occurrences, it'd be nice to emit
920
999
// a combined read-write occurrence. However, that would require complicating the code a
921
1000
// bit, so let's leave it as-is for now.
922
- this ->emitLocalOccurrence (cfg, bb, arg.occurrence (), ValueCategory::RValue);
1001
+ this ->emitLocalOccurrence (cfg, bb, arg.occurrence (), ValueCategory::RValue,
1002
+ core::TypePtr ());
923
1003
}
924
1004
925
1005
break ;
0 commit comments