Skip to content

Commit d367088

Browse files
committed
[SourceKit] Allow cursorinfo to take a USR instead of an offset
This eventually calls the code from ReconstructType to try to find the Decl for a USR. For now, only works in a file, not a generated interface. rdar://problem/25017817
1 parent 0acc0a8 commit d367088

File tree

6 files changed

+232
-7
lines changed

6 files changed

+232
-7
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// The RUN lines are at the bottom in case we ever need to rely on line:col info.
2+
import Foo
3+
import FooSwiftModule
4+
5+
var global: Int
6+
7+
struct S1 {}
8+
9+
func foo(x: FooStruct1) -> S1 {}
10+
11+
// RUN: rm -rf %t
12+
// RUN: mkdir %t
13+
// RUN: %swiftc_driver -emit-module -o %t/FooSwiftModule.swiftmodule %S/Inputs/FooSwiftModule.swift
14+
15+
// Sanity check that we have identical responses when things work.
16+
// RUN: %sourcekitd-test -req=cursor -pos=5:5 %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s > %t.from_offset.txt
17+
// RUN: %sourcekitd-test -req=cursor -usr "s:v10cursor_usr6globalSi" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s > %t.from_usr.txt
18+
// RUN: FileCheck %s -check-prefix=CHECK_SANITY1 < %t.from_offset.txt
19+
// RUN: FileCheck %s -check-prefix=CHECK_SANITY1 < %t.from_usr.txt
20+
// RUN: diff -u %t.from_usr.txt %t.from_offset.txt
21+
// CHECK_SANITY1: source.lang.swift.decl.var.global (5:5-5:11)
22+
// CHECK_SANITY1-NEXT: global
23+
// CHECK_SANITY1-NEXT: s:v10cursor_usr6globalSi
24+
// CHECK_SANITY1-NEXT: Int
25+
// CHECK_SANITY1-NEXT: <Declaration>var global: <Type usr="s:Si">Int</Type></Declaration>
26+
// CHECK_SANITY1-NEXT: <decl.var.global><syntaxtype.keyword>var</syntaxtype.keyword> <decl.name>global</decl.name>: <decl.var.type><ref.struct usr="s:Si">Int</ref.struct></decl.var.type></decl.var.global>
27+
28+
// Bogus USR.
29+
// RUN: %sourcekitd-test -req=cursor -usr "s:blahblahblah" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=EMPTY
30+
// Missing s: prefix.
31+
// RUN: %sourcekitd-test -req=cursor -usr "v10cursor_usr6globalSi" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=EMPTY
32+
// FIXME: no support for clang USRs.
33+
// RUN: %sourcekitd-test -req=cursor -usr "c:@S@FooStruct1" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=EMPTY
34+
// EMPTY: <empty cursor info>
35+
36+
// FIXME: missing symbol shows up as some other part of the USR (the type here).
37+
// RUN: %sourcekitd-test -req=cursor -usr "s:v10cursor_usr11global_noneSi" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=SHOULD_BE_EMPTY
38+
// SHOULD_BE_EMPTY: source.lang.swift.decl.struct ()
39+
// SHOULD_BE_EMPTY: Int
40+
41+
// RUN: %sourcekitd-test -req=cursor -usr "s:V10cursor_usr2S1" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=CHECK1
42+
// CHECK1: source.lang.swift.decl.struct (7:8-7:10)
43+
// CHECK1: S1
44+
// CHECK1: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>S1</decl.name></decl.struct>
45+
46+
// RUN: %sourcekitd-test -req=cursor -usr "s:F14FooSwiftModule12fooSwiftFuncFT_Si" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=CHECK2
47+
// CHECK2: source.lang.swift.decl.function.free ()
48+
// CHECK2: fooSwiftFunc()
49+
// CHECK2: () -> Int
50+
// CHECK2: FooSwiftModule
51+
// CHECK2: <decl.function.free><syntaxtype.keyword>func</syntaxtype.keyword> <decl.name>fooSwiftFunc</decl.name>() -&gt; <decl.function.returntype><ref.struct usr="s:Si">Int</ref.struct></decl.function.returntype></decl.function.free>
52+
53+
// RUN: %sourcekitd-test -req=cursor -usr "s:F10cursor_usr3fooFVSC10FooStruct1VS_2S1" %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s | FileCheck %s -check-prefix=CHECK3
54+
// CHECK3: source.lang.swift.decl.function.free (9:6-9:24)
55+
// CHECK3: foo(_:)
56+
// CHECK3: (FooStruct1) -> S1
57+
// CHECK3: <decl.function.free><syntaxtype.keyword>func</syntaxtype.keyword> <decl.name>foo</decl.name>(<decl.var.parameter><decl.var.parameter.name>x</decl.var.parameter.name>: <decl.var.parameter.type><ref.struct usr="c:@S@FooStruct1">FooStruct1</ref.struct></decl.var.parameter.type></decl.var.parameter>) -&gt; <decl.function.returntype><ref.struct usr="s:V10cursor_usr2S1">S1</ref.struct></decl.function.returntype></decl.function.free>

tools/SourceKit/include/SourceKit/Core/LangSupport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,11 @@ class LangSupport {
458458
ArrayRef<const char *> Args,
459459
std::function<void(const CursorInfo &)> Receiver) = 0;
460460

461+
virtual void
462+
getCursorInfoFromUSR(StringRef Filename, StringRef USR,
463+
ArrayRef<const char *> Args,
464+
std::function<void(const CursorInfo &)> Receiver) = 0;
465+
461466
virtual void findRelatedIdentifiersInFile(StringRef Filename,
462467
unsigned Offset,
463468
ArrayRef<const char *> Args,

tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ class SwiftLangSupport : public LangSupport {
354354
ArrayRef<const char *> Args,
355355
std::function<void(const CursorInfo &)> Receiver) override;
356356

357+
void getCursorInfoFromUSR(
358+
StringRef Filename, StringRef USR, ArrayRef<const char *> Args,
359+
std::function<void(const CursorInfo &)> Receiver) override;
360+
357361
void findRelatedIdentifiersInFile(StringRef Filename, unsigned Offset,
358362
ArrayRef<const char *> Args,
359363
std::function<void(const RelatedIdentsInfo &)> Receiver) override;

tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,151 @@ void SwiftLangSupport::getCursorInfo(
994994
Receiver);
995995
}
996996

997+
static void
998+
resolveCursorFromUSR(SwiftLangSupport &Lang, StringRef InputFile, StringRef USR,
999+
SwiftInvocationRef Invok, bool TryExistingAST,
1000+
std::function<void(const CursorInfo &)> Receiver) {
1001+
assert(Invok);
1002+
1003+
class CursorInfoConsumer : public SwiftASTConsumer {
1004+
std::string InputFile;
1005+
StringRef USR;
1006+
SwiftLangSupport &Lang;
1007+
SwiftInvocationRef ASTInvok;
1008+
const bool TryExistingAST;
1009+
std::function<void(const CursorInfo &)> Receiver;
1010+
SmallVector<ImmutableTextSnapshotRef, 4> PreviousASTSnaps;
1011+
1012+
public:
1013+
CursorInfoConsumer(StringRef InputFile, StringRef USR,
1014+
SwiftLangSupport &Lang, SwiftInvocationRef ASTInvok,
1015+
bool TryExistingAST,
1016+
std::function<void(const CursorInfo &)> Receiver)
1017+
: InputFile(InputFile), USR(USR), Lang(Lang),
1018+
ASTInvok(std::move(ASTInvok)), TryExistingAST(TryExistingAST),
1019+
Receiver(std::move(Receiver)) {}
1020+
1021+
bool canUseASTWithSnapshots(
1022+
ArrayRef<ImmutableTextSnapshotRef> Snapshots) override {
1023+
if (!TryExistingAST) {
1024+
LOG_INFO_FUNC(High, "will resolve using up-to-date AST");
1025+
return false;
1026+
}
1027+
1028+
if (!Snapshots.empty()) {
1029+
PreviousASTSnaps.append(Snapshots.begin(), Snapshots.end());
1030+
LOG_INFO_FUNC(High, "will try existing AST");
1031+
return true;
1032+
}
1033+
1034+
LOG_INFO_FUNC(High, "will resolve using up-to-date AST");
1035+
return false;
1036+
}
1037+
1038+
void handlePrimaryAST(ASTUnitRef AstUnit) override {
1039+
auto &CompIns = AstUnit->getCompilerInstance();
1040+
Module *MainModule = CompIns.getMainModule();
1041+
1042+
unsigned BufferID =
1043+
AstUnit->getPrimarySourceFile().getBufferID().getValue();
1044+
1045+
trace::TracedOperation TracedOp;
1046+
if (trace::enabled()) {
1047+
trace::SwiftInvocation SwiftArgs;
1048+
ASTInvok->raw(SwiftArgs.Args.Args, SwiftArgs.Args.PrimaryFile);
1049+
trace::initTraceFiles(SwiftArgs, CompIns);
1050+
TracedOp.start(trace::OperationKind::CursorInfoForSource, SwiftArgs,
1051+
{std::make_pair("USR", USR)});
1052+
}
1053+
1054+
std::string mangledName(USR);
1055+
if (USR.startswith("s:")) {
1056+
mangledName.replace(0, 2, "_T");
1057+
} else if (USR.startswith("c:")) {
1058+
LOG_WARN_FUNC("lookup for C/C++/ObjC USRs not implemented");
1059+
Receiver({});
1060+
return;
1061+
} else if (!USR.startswith("_T")) {
1062+
LOG_WARN_FUNC("unknown USR prefix");
1063+
Receiver({});
1064+
return;
1065+
}
1066+
1067+
auto &context = CompIns.getASTContext();
1068+
std::string error;
1069+
Decl *D = ide::getDeclFromMangledSymbolName(context, mangledName, error);
1070+
1071+
if (!D) {
1072+
Receiver({});
1073+
return;
1074+
}
1075+
1076+
CompilerInvocation CompInvok;
1077+
ASTInvok->applyTo(CompInvok);
1078+
1079+
if (auto *M = dyn_cast<ModuleDecl>(D)) {
1080+
passCursorInfoForModule(M, Lang.getIFaceGenContexts(), CompInvok,
1081+
Receiver);
1082+
} else if (auto *VD = dyn_cast<ValueDecl>(D)) {
1083+
bool Failed =
1084+
passCursorInfoForDecl(VD, MainModule, VD->getType(),
1085+
/*isRef=*/false, BufferID, Lang, CompInvok,
1086+
PreviousASTSnaps, Receiver);
1087+
if (Failed) {
1088+
if (!PreviousASTSnaps.empty()) {
1089+
// Attempt again using the up-to-date AST.
1090+
resolveCursorFromUSR(Lang, InputFile, USR, ASTInvok,
1091+
/*TryExistingAST=*/false, Receiver);
1092+
} else {
1093+
Receiver({});
1094+
}
1095+
}
1096+
}
1097+
}
1098+
1099+
void cancelled() override {
1100+
CursorInfo Info;
1101+
Info.IsCancelled = true;
1102+
Receiver(Info);
1103+
}
1104+
1105+
void failed(StringRef Error) override {
1106+
LOG_WARN_FUNC("cursor info failed: " << Error);
1107+
Receiver({});
1108+
}
1109+
};
1110+
1111+
auto Consumer = std::make_shared<CursorInfoConsumer>(
1112+
InputFile, USR, Lang, Invok, TryExistingAST, Receiver);
1113+
/// FIXME: When request cancellation is implemented and Xcode adopts it,
1114+
/// don't use 'OncePerASTToken'.
1115+
static const char OncePerASTToken = 0;
1116+
Lang.getASTManager().processASTAsync(Invok, std::move(Consumer),
1117+
&OncePerASTToken);
1118+
}
1119+
1120+
void SwiftLangSupport::getCursorInfoFromUSR(
1121+
StringRef filename, StringRef USR, ArrayRef<const char *> args,
1122+
std::function<void(const CursorInfo &)> receiver) {
1123+
if (auto IFaceGenRef = IFaceGenContexts.get(filename)) {
1124+
LOG_WARN_FUNC("info from usr for generated interface not implemented yet");
1125+
receiver({});
1126+
return;
1127+
}
1128+
1129+
std::string error;
1130+
SwiftInvocationRef invok = ASTMgr->getInvocation(args, filename, error);
1131+
if (!invok) {
1132+
// FIXME: Report it as failed request.
1133+
LOG_WARN_FUNC("failed to create an ASTInvocation: " << error);
1134+
receiver({});
1135+
return;
1136+
}
1137+
1138+
resolveCursorFromUSR(*this, filename, USR, invok, /*TryExistingAST=*/true,
1139+
receiver);
1140+
}
1141+
9971142
//===----------------------------------------------------------------------===//
9981143
// SwiftLangSupport::findUSRRange
9991144
//===----------------------------------------------------------------------===//

tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ static int handleTestInvocation(ArrayRef<const char *> Args,
384384
getBufferForFilename(SourceFile)->getBuffer(), SourceFile);
385385
}
386386

387+
// FIXME: we should detect if offset is required but not set.
387388
unsigned ByteOffset = Opts.Offset;
388389
if (Opts.Line != 0) {
389390
ByteOffset = resolveFromLineCol(Opts.Line, Opts.Col, SourceBuf.get());
@@ -486,7 +487,11 @@ static int handleTestInvocation(ArrayRef<const char *> Args,
486487

487488
case SourceKitRequest::CursorInfo:
488489
sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestCursorInfo);
489-
sourcekitd_request_dictionary_set_int64(Req, KeyOffset, ByteOffset);
490+
if (!Opts.USR.empty()) {
491+
sourcekitd_request_dictionary_set_string(Req, KeyUSR, Opts.USR.c_str());
492+
} else {
493+
sourcekitd_request_dictionary_set_int64(Req, KeyOffset, ByteOffset);
494+
}
490495
break;
491496

492497
case SourceKitRequest::RelatedIdents:

tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -701,13 +701,22 @@ handleSemanticRequest(RequestDict Req,
701701
return Rec(createErrorRequestFailed("semantic editor is disabled"));
702702

703703
if (ReqUID == RequestCursorInfo) {
704-
int64_t Offset;
705-
if (Req.getInt64(KeyOffset, Offset, /*isOptional=*/false))
706-
return Rec(createErrorRequestInvalid("missing 'key.offset'"));
707704
LangSupport &Lang = getGlobalContext().getSwiftLangSupport();
708-
return Lang.getCursorInfo(
709-
*SourceFile, Offset, Args,
710-
[Rec](const CursorInfo &Info) { reportCursorInfo(Info, Rec); });
705+
706+
int64_t Offset;
707+
if (!Req.getInt64(KeyOffset, Offset, /*isOptional=*/false)) {
708+
return Lang.getCursorInfo(
709+
*SourceFile, Offset, Args,
710+
[Rec](const CursorInfo &Info) { reportCursorInfo(Info, Rec); });
711+
}
712+
if (auto USR = Req.getString(KeyUSR)) {
713+
return Lang.getCursorInfoFromUSR(
714+
*SourceFile, *USR, Args,
715+
[Rec](const CursorInfo &Info) { reportCursorInfo(Info, Rec); });
716+
}
717+
718+
return Rec(createErrorRequestInvalid(
719+
"either 'key.offset' or 'key.usr' is required"));
711720
}
712721

713722
if (ReqUID == RequestRelatedIdents) {

0 commit comments

Comments
 (0)