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) {