diff --git a/test/SourceKit/SourceDocInfo/cursor_usr.swift b/test/SourceKit/SourceDocInfo/cursor_usr.swift
new file mode 100644
index 00000000000..bdce57bb228
--- /dev/null
+++ b/test/SourceKit/SourceDocInfo/cursor_usr.swift
@@ -0,0 +1,57 @@
+// The RUN lines are at the bottom in case we ever need to rely on line:col info.
+import Foo
+import FooSwiftModule
+
+var global: Int
+
+struct S1 {}
+
+func foo(x: FooStruct1) -> S1 {}
+
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: %swiftc_driver -emit-module -o %t/FooSwiftModule.swiftmodule %S/Inputs/FooSwiftModule.swift
+
+// Sanity check that we have identical responses when things work.
+// RUN: %sourcekitd-test -req=cursor -pos=5:5 %s -- -I %t -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %s > %t.from_offset.txt
+// 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
+// RUN: FileCheck %s -check-prefix=CHECK_SANITY1 < %t.from_offset.txt
+// RUN: FileCheck %s -check-prefix=CHECK_SANITY1 < %t.from_usr.txt
+// RUN: diff -u %t.from_usr.txt %t.from_offset.txt
+// CHECK_SANITY1: source.lang.swift.decl.var.global (5:5-5:11)
+// CHECK_SANITY1-NEXT: global
+// CHECK_SANITY1-NEXT: s:v10cursor_usr6globalSi
+// CHECK_SANITY1-NEXT: Int
+// CHECK_SANITY1-NEXT: var global: Int
+// CHECK_SANITY1-NEXT: var global: Int
+
+// Bogus USR.
+// 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
+// Missing s: prefix.
+// 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
+// FIXME: no support for clang USRs.
+// 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
+// EMPTY:
+
+// FIXME: missing symbol shows up as some other part of the USR (the type here).
+// 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
+// SHOULD_BE_EMPTY: source.lang.swift.decl.struct ()
+// SHOULD_BE_EMPTY: Int
+
+// 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
+// CHECK1: source.lang.swift.decl.struct (7:8-7:10)
+// CHECK1: S1
+// CHECK1: struct S1
+
+// 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
+// CHECK2: source.lang.swift.decl.function.free ()
+// CHECK2: fooSwiftFunc()
+// CHECK2: () -> Int
+// CHECK2: FooSwiftModule
+// CHECK2: func fooSwiftFunc() -> Int
+
+// 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
+// CHECK3: source.lang.swift.decl.function.free (9:6-9:24)
+// CHECK3: foo(_:)
+// CHECK3: (FooStruct1) -> S1
+// CHECK3: func foo(x: FooStruct1) -> S1
diff --git a/tools/SourceKit/include/SourceKit/Core/LangSupport.h b/tools/SourceKit/include/SourceKit/Core/LangSupport.h
index b63a9a9a261..4d4bee80def 100644
--- a/tools/SourceKit/include/SourceKit/Core/LangSupport.h
+++ b/tools/SourceKit/include/SourceKit/Core/LangSupport.h
@@ -458,6 +458,11 @@ public:
ArrayRef Args,
std::function Receiver) = 0;
+ virtual void
+ getCursorInfoFromUSR(StringRef Filename, StringRef USR,
+ ArrayRef Args,
+ std::function Receiver) = 0;
+
virtual void findRelatedIdentifiersInFile(StringRef Filename,
unsigned Offset,
ArrayRef Args,
diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h
index 768d9e903ae..c8f3aa2a1e5 100644
--- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h
+++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h
@@ -354,6 +354,10 @@ public:
ArrayRef Args,
std::function Receiver) override;
+ void getCursorInfoFromUSR(
+ StringRef Filename, StringRef USR, ArrayRef Args,
+ std::function Receiver) override;
+
void findRelatedIdentifiersInFile(StringRef Filename, unsigned Offset,
ArrayRef Args,
std::function Receiver) override;
diff --git a/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp
index eb7f4b1b970..035f32fcfc4 100644
--- a/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp
+++ b/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp
@@ -994,6 +994,151 @@ void SwiftLangSupport::getCursorInfo(
Receiver);
}
+static void
+resolveCursorFromUSR(SwiftLangSupport &Lang, StringRef InputFile, StringRef USR,
+ SwiftInvocationRef Invok, bool TryExistingAST,
+ std::function Receiver) {
+ assert(Invok);
+
+ class CursorInfoConsumer : public SwiftASTConsumer {
+ std::string InputFile;
+ StringRef USR;
+ SwiftLangSupport ⟪
+ SwiftInvocationRef ASTInvok;
+ const bool TryExistingAST;
+ std::function Receiver;
+ SmallVector PreviousASTSnaps;
+
+ public:
+ CursorInfoConsumer(StringRef InputFile, StringRef USR,
+ SwiftLangSupport &Lang, SwiftInvocationRef ASTInvok,
+ bool TryExistingAST,
+ std::function Receiver)
+ : InputFile(InputFile), USR(USR), Lang(Lang),
+ ASTInvok(std::move(ASTInvok)), TryExistingAST(TryExistingAST),
+ Receiver(std::move(Receiver)) {}
+
+ bool canUseASTWithSnapshots(
+ ArrayRef Snapshots) override {
+ if (!TryExistingAST) {
+ LOG_INFO_FUNC(High, "will resolve using up-to-date AST");
+ return false;
+ }
+
+ if (!Snapshots.empty()) {
+ PreviousASTSnaps.append(Snapshots.begin(), Snapshots.end());
+ LOG_INFO_FUNC(High, "will try existing AST");
+ return true;
+ }
+
+ LOG_INFO_FUNC(High, "will resolve using up-to-date AST");
+ return false;
+ }
+
+ void handlePrimaryAST(ASTUnitRef AstUnit) override {
+ auto &CompIns = AstUnit->getCompilerInstance();
+ Module *MainModule = CompIns.getMainModule();
+
+ unsigned BufferID =
+ AstUnit->getPrimarySourceFile().getBufferID().getValue();
+
+ trace::TracedOperation TracedOp;
+ if (trace::enabled()) {
+ trace::SwiftInvocation SwiftArgs;
+ ASTInvok->raw(SwiftArgs.Args.Args, SwiftArgs.Args.PrimaryFile);
+ trace::initTraceFiles(SwiftArgs, CompIns);
+ TracedOp.start(trace::OperationKind::CursorInfoForSource, SwiftArgs,
+ {std::make_pair("USR", USR)});
+ }
+
+ std::string mangledName(USR);
+ if (USR.startswith("s:")) {
+ mangledName.replace(0, 2, "_T");
+ } else if (USR.startswith("c:")) {
+ LOG_WARN_FUNC("lookup for C/C++/ObjC USRs not implemented");
+ Receiver({});
+ return;
+ } else if (!USR.startswith("_T")) {
+ LOG_WARN_FUNC("unknown USR prefix");
+ Receiver({});
+ return;
+ }
+
+ auto &context = CompIns.getASTContext();
+ std::string error;
+ Decl *D = ide::getDeclFromMangledSymbolName(context, mangledName, error);
+
+ if (!D) {
+ Receiver({});
+ return;
+ }
+
+ CompilerInvocation CompInvok;
+ ASTInvok->applyTo(CompInvok);
+
+ if (auto *M = dyn_cast(D)) {
+ passCursorInfoForModule(M, Lang.getIFaceGenContexts(), CompInvok,
+ Receiver);
+ } else if (auto *VD = dyn_cast(D)) {
+ bool Failed =
+ passCursorInfoForDecl(VD, MainModule, VD->getType(),
+ /*isRef=*/false, BufferID, Lang, CompInvok,
+ PreviousASTSnaps, Receiver);
+ if (Failed) {
+ if (!PreviousASTSnaps.empty()) {
+ // Attempt again using the up-to-date AST.
+ resolveCursorFromUSR(Lang, InputFile, USR, ASTInvok,
+ /*TryExistingAST=*/false, Receiver);
+ } else {
+ Receiver({});
+ }
+ }
+ }
+ }
+
+ void cancelled() override {
+ CursorInfo Info;
+ Info.IsCancelled = true;
+ Receiver(Info);
+ }
+
+ void failed(StringRef Error) override {
+ LOG_WARN_FUNC("cursor info failed: " << Error);
+ Receiver({});
+ }
+ };
+
+ auto Consumer = std::make_shared(
+ InputFile, USR, Lang, Invok, TryExistingAST, Receiver);
+ /// FIXME: When request cancellation is implemented and Xcode adopts it,
+ /// don't use 'OncePerASTToken'.
+ static const char OncePerASTToken = 0;
+ Lang.getASTManager().processASTAsync(Invok, std::move(Consumer),
+ &OncePerASTToken);
+}
+
+void SwiftLangSupport::getCursorInfoFromUSR(
+ StringRef filename, StringRef USR, ArrayRef args,
+ std::function receiver) {
+ if (auto IFaceGenRef = IFaceGenContexts.get(filename)) {
+ LOG_WARN_FUNC("info from usr for generated interface not implemented yet");
+ receiver({});
+ return;
+ }
+
+ std::string error;
+ SwiftInvocationRef invok = ASTMgr->getInvocation(args, filename, error);
+ if (!invok) {
+ // FIXME: Report it as failed request.
+ LOG_WARN_FUNC("failed to create an ASTInvocation: " << error);
+ receiver({});
+ return;
+ }
+
+ resolveCursorFromUSR(*this, filename, USR, invok, /*TryExistingAST=*/true,
+ receiver);
+}
+
//===----------------------------------------------------------------------===//
// SwiftLangSupport::findUSRRange
//===----------------------------------------------------------------------===//
diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp
index 935493e6154..5260c5f7b8a 100644
--- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp
+++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp
@@ -384,6 +384,7 @@ static int handleTestInvocation(ArrayRef Args,
getBufferForFilename(SourceFile)->getBuffer(), SourceFile);
}
+ // FIXME: we should detect if offset is required but not set.
unsigned ByteOffset = Opts.Offset;
if (Opts.Line != 0) {
ByteOffset = resolveFromLineCol(Opts.Line, Opts.Col, SourceBuf.get());
@@ -486,7 +487,11 @@ static int handleTestInvocation(ArrayRef Args,
case SourceKitRequest::CursorInfo:
sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestCursorInfo);
- sourcekitd_request_dictionary_set_int64(Req, KeyOffset, ByteOffset);
+ if (!Opts.USR.empty()) {
+ sourcekitd_request_dictionary_set_string(Req, KeyUSR, Opts.USR.c_str());
+ } else {
+ sourcekitd_request_dictionary_set_int64(Req, KeyOffset, ByteOffset);
+ }
break;
case SourceKitRequest::RelatedIdents:
diff --git a/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp b/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp
index 633eb0fec11..315f0e599ae 100644
--- a/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp
+++ b/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp
@@ -701,13 +701,22 @@ handleSemanticRequest(RequestDict Req,
return Rec(createErrorRequestFailed("semantic editor is disabled"));
if (ReqUID == RequestCursorInfo) {
- int64_t Offset;
- if (Req.getInt64(KeyOffset, Offset, /*isOptional=*/false))
- return Rec(createErrorRequestInvalid("missing 'key.offset'"));
LangSupport &Lang = getGlobalContext().getSwiftLangSupport();
- return Lang.getCursorInfo(
- *SourceFile, Offset, Args,
- [Rec](const CursorInfo &Info) { reportCursorInfo(Info, Rec); });
+
+ int64_t Offset;
+ if (!Req.getInt64(KeyOffset, Offset, /*isOptional=*/false)) {
+ return Lang.getCursorInfo(
+ *SourceFile, Offset, Args,
+ [Rec](const CursorInfo &Info) { reportCursorInfo(Info, Rec); });
+ }
+ if (auto USR = Req.getString(KeyUSR)) {
+ return Lang.getCursorInfoFromUSR(
+ *SourceFile, *USR, Args,
+ [Rec](const CursorInfo &Info) { reportCursorInfo(Info, Rec); });
+ }
+
+ return Rec(createErrorRequestInvalid(
+ "either 'key.offset' or 'key.usr' is required"));
}
if (ReqUID == RequestRelatedIdents) {