From 2fcb24e716eaeac9e96555c0dc1e575b98469ae7 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 7 Oct 2021 18:03:29 +0200 Subject: [PATCH 01/12] [CodeCompletion] Refactor how code completion results are returned to support cancellation This refactors a bunch of code-completion methods around `performOperation` to return their results via a callback only instead of the current mixed approach of indicating failure via a return value, returning an error string as an inout parameter and success results via a callback. The new guarantee should be that the callback is always called exactly once on control flow graph. Other than a support for passing the (currently unused) cancelled state through the different instance, there should be no functionality change. --- include/swift/IDE/CancellableResult.h | 148 +++++++++++ include/swift/IDE/CompletionInstance.h | 38 ++- include/swift/IDE/ConformingMethodList.h | 2 - include/swift/IDE/TypeContextInfo.h | 2 - lib/IDE/CompletionInstance.cpp | 54 ++-- .../include/SourceKit/Core/LangSupport.h | 3 + .../lib/SwiftLang/SwiftCompletion.cpp | 235 ++++++++++++++---- .../SwiftLang/SwiftConformingMethodList.cpp | 106 ++++++-- .../lib/SwiftLang/SwiftLangSupport.cpp | 28 ++- .../lib/SwiftLang/SwiftLangSupport.h | 11 +- .../lib/SwiftLang/SwiftTypeContextInfo.cpp | 99 ++++++-- .../tools/sourcekitd/lib/API/Requests.cpp | 55 +++- tools/swift-ide-test/swift-ide-test.cpp | 102 +++++--- 13 files changed, 695 insertions(+), 188 deletions(-) create mode 100644 include/swift/IDE/CancellableResult.h diff --git a/include/swift/IDE/CancellableResult.h b/include/swift/IDE/CancellableResult.h new file mode 100644 index 0000000000000..5ef6177df3b95 --- /dev/null +++ b/include/swift/IDE/CancellableResult.h @@ -0,0 +1,148 @@ +//===--- CancellableResult.h ------------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_IDE_CANCELLABLE_RESULT_H +#define SWIFT_IDE_CANCELLABLE_RESULT_H + +#include + +namespace swift { + +namespace ide { + +enum class CancellableResultKind { Success, Failure, Cancelled }; + +/// A result type that can carry one be in one of the following states: +/// - Success and carry a value of \c ResultType +/// - Failure and carry an error description +/// - Cancelled in case the operation that produced the result was cancelled +/// +/// Essentially this emulates an enum with associated values as follows +/// \code +/// enum CancellableResult { +/// case success(ResultType) +/// case failure(String) +/// case cancelled +/// } +/// \endcode +/// +/// The implementation is inspired by llvm::optional_detail::OptionalStorage +template +class CancellableResult { + CancellableResultKind Kind; + union { + /// If \c Kind == Success, carries the result. + ResultType Result; + /// If \c Kind == Error, carries the error description. + std::string Error; + /// If \c Kind == Cancelled, this union is not initialized. + char Empty; + }; + + CancellableResult(ResultType Result) + : Kind(CancellableResultKind::Success), Result(Result) {} + + CancellableResult(std::string Error) + : Kind(CancellableResultKind::Failure), Error(Error) {} + + explicit CancellableResult() + : Kind(CancellableResultKind::Cancelled), Empty() {} + +public: + CancellableResult(const CancellableResult &Other) : Kind(Other.Kind), Empty() { + switch (Kind) { + case CancellableResultKind::Success: + ::new ((void *)std::addressof(Result)) ResultType(Other.Result); + break; + case CancellableResultKind::Failure: + ::new ((void *)std::addressof(Error)) std::string(Other.Error); + break; + case CancellableResultKind::Cancelled: + break; + } + } + + CancellableResult(CancellableResult &&Other) : Kind(Other.Kind), Empty() { + switch (Kind) { + case CancellableResultKind::Success: + ::new ((void *)std::addressof(Result)) + ResultType(std::move(Other.Result)); + break; + case CancellableResultKind::Failure: + Error = std::move(Other.Error); + break; + case CancellableResultKind::Cancelled: + break; + } + } + + ~CancellableResult() { + using std::string; + switch (Kind) { + case CancellableResultKind::Success: + Result.~ResultType(); + break; + case CancellableResultKind::Failure: + Error.~string(); + break; + case CancellableResultKind::Cancelled: + break; + } + } + + /// Construct a \c CancellableResult that carries a successful result. + static CancellableResult success(ResultType Result) { + return std::move(CancellableResult(ResultType(Result))); + } + + /// Construct a \c CancellableResult that carries the error message of a + /// failure. + static CancellableResult failure(std::string Error) { + return std::move(CancellableResult(Error)); + } + + /// Construct a \c CancellableResult representing that the producing operation + /// was cancelled. + static CancellableResult cancelled() { + return std::move(CancellableResult()); + } + + /// Return the result kind this \c CancellableResult represents: success, + /// failure or cancelled. + CancellableResultKind getKind() { return Kind; } + + /// Assuming that the result represents success, return the underlying result + /// value. + ResultType &getResult() { + assert(getKind() == CancellableResultKind::Success); + return Result; + } + + /// Assuming that the result represents success, retrieve members of the + /// underlying result value. + ResultType *operator->() { return &getResult(); } + + /// Assuming that the result represents success, return the underlying result + /// value. + ResultType &operator*() { return getResult(); } + + /// Assuming that the result represents a failure, return the error message. + std::string getError() { + assert(getKind() == CancellableResultKind::Failure); + return Error; + } +}; + +} // namespace ide +} // namespace swift + +#endif // SWIFT_IDE_CANCELLABLE_RESULT_H diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index 91328bbc08a1e..b5f2d9786bc23 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -14,6 +14,7 @@ #define SWIFT_IDE_COMPLETIONINSTANCE_H #include "swift/Frontend/Frontend.h" +#include "swift/IDE/CancellableResult.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringRef.h" @@ -35,6 +36,14 @@ makeCodeCompletionMemoryBuffer(const llvm::MemoryBuffer *origBuf, unsigned &Offset, llvm::StringRef bufferIdentifier); +/// The result returned via the callback from the perform*Operation methods. +struct CompletionInstanceResult { + /// The compiler instance that is prepared for the second pass. + CompilerInstance &CI; + /// Whether an AST was reused. + bool DidReuseAST; +}; + /// Manages \c CompilerInstance for completion like operations. class CompletionInstance { struct Options { @@ -58,27 +67,31 @@ class CompletionInstance { /// Calls \p Callback with cached \c CompilerInstance if it's usable for the /// specified completion request. - /// Returns \c if the callback was called. Returns \c false if the compiler - /// argument has changed, primary file is not the same, the \c Offset is not - /// in function bodies, or the interface hash of the file has changed. + /// Returns \c true if performing the cached operation was possible. Returns + /// \c false if the compiler argument has changed, primary file is not the + /// same, the \c Offset is not in function bodies, or the interface hash of + /// the file has changed. + /// \p Callback will be called if and only if this function returns \c true. bool performCachedOperationIfPossible( llvm::hash_code ArgsHash, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, DiagnosticConsumer *DiagC, - llvm::function_ref Callback); + llvm::function_ref)> + Callback); /// Calls \p Callback with new \c CompilerInstance for the completion /// request. The \c CompilerInstace passed to the callback already performed /// the first pass. /// Returns \c false if it fails to setup the \c CompilerInstance. - bool performNewOperation( + void performNewOperation( llvm::Optional ArgsHash, swift::CompilerInvocation &Invocation, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, - std::string &Error, DiagnosticConsumer *DiagC, - llvm::function_ref Callback); + DiagnosticConsumer *DiagC, + llvm::function_ref)> + Callback); public: CompletionInstance() : CachedCIShouldBeInvalidated(false) {} @@ -94,18 +107,19 @@ class CompletionInstance { /// second pass. \p Callback is resposible to perform the second pass on it. /// The \c CompilerInstance may be reused from the previous completions, /// and may be cached for the next completion. - /// Return \c true if \p is successfully called, \c it fails. In failure - /// cases \p Error is populated with an error message. + /// In case of failure or cancellation, the callback receives the + /// corresponding failed or cancelled result. /// /// NOTE: \p Args is only used for checking the equaity of the invocation. /// Since this function assumes that it is already normalized, exact the same /// arguments including their order is considered as the same invocation. - bool performOperation( + void performOperation( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, - std::string &Error, DiagnosticConsumer *DiagC, - llvm::function_ref Callback); + DiagnosticConsumer *DiagC, + llvm::function_ref)> + Callback); }; } // namespace ide diff --git a/include/swift/IDE/ConformingMethodList.h b/include/swift/IDE/ConformingMethodList.h index d755f080798ae..1dbe1c3770972 100644 --- a/include/swift/IDE/ConformingMethodList.h +++ b/include/swift/IDE/ConformingMethodList.h @@ -42,7 +42,6 @@ class ConformingMethodListConsumer { public: virtual ~ConformingMethodListConsumer() {} virtual void handleResult(const ConformingMethodListResult &result) = 0; - virtual void setReusingASTContext(bool flag) = 0; }; /// Printing consumer @@ -54,7 +53,6 @@ class PrintingConformingMethodListConsumer PrintingConformingMethodListConsumer(llvm::raw_ostream &OS) : OS(OS) {} void handleResult(const ConformingMethodListResult &result) override; - void setReusingASTContext(bool flag) override {} }; /// Create a factory for code completion callbacks. diff --git a/include/swift/IDE/TypeContextInfo.h b/include/swift/IDE/TypeContextInfo.h index ed12906edf303..2bb8c95053710 100644 --- a/include/swift/IDE/TypeContextInfo.h +++ b/include/swift/IDE/TypeContextInfo.h @@ -38,7 +38,6 @@ class TypeContextInfoConsumer { public: virtual ~TypeContextInfoConsumer() {} virtual void handleResults(ArrayRef) = 0; - virtual void setReusingASTContext(bool flag) = 0; }; /// Printing consumer @@ -49,7 +48,6 @@ class PrintingTypeContextInfoConsumer : public TypeContextInfoConsumer { PrintingTypeContextInfoConsumer(llvm::raw_ostream &OS) : OS(OS) {} void handleResults(ArrayRef) override; - void setReusingASTContext(bool flag) override {} }; /// Create a factory for code completion callbacks. diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index 0b093c556bb2c..4a782d4926d81 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -281,11 +281,12 @@ static bool areAnyDependentFilesInvalidated( } // namespace bool CompletionInstance::performCachedOperationIfPossible( - llvm::hash_code ArgsHash, + llvm::hash_code ArgsHash, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, DiagnosticConsumer *DiagC, - llvm::function_ref Callback) { + llvm::function_ref)> + Callback) { llvm::PrettyStackTraceString trace( "While performing cached completion if possible"); @@ -504,7 +505,8 @@ bool CompletionInstance::performCachedOperationIfPossible( if (DiagC) CI.addDiagnosticConsumer(DiagC); - Callback(CI, /*reusingASTContext=*/true); + Callback(CancellableResult::success( + {CI, /*reusingASTContext=*/true})); if (DiagC) CI.removeDiagnosticConsumer(DiagC); @@ -515,12 +517,13 @@ bool CompletionInstance::performCachedOperationIfPossible( return true; } -bool CompletionInstance::performNewOperation( +void CompletionInstance::performNewOperation( Optional ArgsHash, swift::CompilerInvocation &Invocation, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, - std::string &Error, DiagnosticConsumer *DiagC, - llvm::function_ref Callback) { + DiagnosticConsumer *DiagC, + llvm::function_ref)> + Callback) { llvm::PrettyStackTraceString trace("While performing new completion"); auto isCachedCompletionRequested = ArgsHash.hasValue(); @@ -548,32 +551,35 @@ bool CompletionInstance::performNewOperation( Invocation.setCodeCompletionPoint(completionBuffer, Offset); if (CI.setup(Invocation)) { - Error = "failed to setup compiler instance"; - return false; + Callback(CancellableResult::failure( + "failed to setup compiler instance")); + return; } registerIDERequestFunctions(CI.getASTContext().evaluator); // If we're expecting a standard library, but there either isn't one, or it // failed to load, let's bail early and hand back an empty completion // result to avoid any downstream crashes. - if (CI.loadStdlibIfNeeded()) - return true; + if (CI.loadStdlibIfNeeded()) { + /// FIXME: Callback is not being called on this path. + return; + } CI.performParseAndResolveImportsOnly(); // If we didn't find a code completion token, bail. auto *state = CI.getCodeCompletionFile()->getDelayedParserState(); if (!state->hasCodeCompletionDelayedDeclState()) - return true; + /// FIXME: Callback is not being called on this path. + return; - Callback(CI, /*reusingASTContext=*/false); + Callback(CancellableResult::success( + {CI, /*ReuisingASTContext=*/false})); } // Cache the compiler instance if fast completion is enabled. if (isCachedCompletionRequested) cacheCompilerInstance(std::move(TheInstance), *ArgsHash); - - return true; } void CompletionInstance::cacheCompilerInstance( @@ -608,12 +614,13 @@ void CompletionInstance::setOptions(CompletionInstance::Options NewOpts) { Opts = NewOpts; } -bool swift::ide::CompletionInstance::performOperation( +void swift::ide::CompletionInstance::performOperation( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, - std::string &Error, DiagnosticConsumer *DiagC, - llvm::function_ref Callback) { + DiagnosticConsumer *DiagC, + llvm::function_ref)> + Callback) { // Always disable source location resolutions from .swiftsourceinfo file // because they're somewhat heavy operations and aren't needed for completion. @@ -637,14 +644,11 @@ bool swift::ide::CompletionInstance::performOperation( if (performCachedOperationIfPossible(ArgsHash, FileSystem, completionBuffer, Offset, DiagC, Callback)) { - return true; + // We were able to reuse a cached AST. Callback has already been invoked + // and we don't need to build a new AST. We are done. + return; } - if(performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer, - Offset, Error, DiagC, Callback)) { - return true; - } - - assert(!Error.empty()); - return false; + performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer, + Offset, DiagC, Callback); } diff --git a/tools/SourceKit/include/SourceKit/Core/LangSupport.h b/tools/SourceKit/include/SourceKit/Core/LangSupport.h index 0a7d777b1e5ec..e70344d92444f 100644 --- a/tools/SourceKit/include/SourceKit/Core/LangSupport.h +++ b/tools/SourceKit/include/SourceKit/Core/LangSupport.h @@ -167,6 +167,7 @@ class CodeCompletionConsumer { virtual ~CodeCompletionConsumer() { } virtual void failed(StringRef ErrDescription) = 0; + virtual void cancelled() = 0; virtual void setCompletionKind(UIdent kind) {}; virtual void setReusingASTContext(bool) = 0; @@ -699,6 +700,7 @@ class TypeContextInfoConsumer { virtual void handleResult(const TypeContextInfoItem &Result) = 0; virtual void failed(StringRef ErrDescription) = 0; + virtual void cancelled() = 0; virtual void setReusingASTContext(bool flag) = 0; }; @@ -726,6 +728,7 @@ class ConformingMethodListConsumer { virtual void handleResult(const ConformingMethodListResult &Result) = 0; virtual void setReusingASTContext(bool flag) = 0; virtual void failed(StringRef ErrDescription) = 0; + virtual void cancelled() = 0; }; class LangSupport { diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp index 00818ff456513..47e28801aaaa9 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp @@ -117,34 +117,98 @@ static UIdent getUIDForCodeCompletionKindToReport(CompletionKind kind) { } } -static bool swiftCodeCompleteImpl( +/// The result returned via the Callback of \c swiftCodeCompleteImpl. +/// If \c HasResults is \c false, code completion did not fail, but did not +/// produce any values either. All other fields of the struct should be ignored +/// in that case. +struct CodeCompleteImplResult { + bool HasResults; + swift::ASTContext *Context; + const swift::CompilerInvocation *Invocation; + swift::ide::CodeCompletionContext *CompletionContext; + ArrayRef RequestedModules; + DeclContext *DC; +}; + +static void swiftCodeCompleteImpl( SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, - unsigned Offset, SwiftCodeCompletionConsumer &SwiftConsumer, - ArrayRef Args, + unsigned Offset, ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, - const CodeCompletion::Options &opts, std::string &Error) { - return Lang.performCompletionLikeOperation( - UnresolvedInputFile, Offset, Args, FileSystem, Error, - [&](CompilerInstance &CI, bool reusingASTContext) { - // Create a factory for code completion callbacks that will feed the - // Consumer. - auto swiftCache = Lang.getCodeCompletionCache(); // Pin the cache. - ide::CodeCompletionContext CompletionContext(swiftCache->getCache()); - CompletionContext.ReusingASTContext = reusingASTContext; - CompletionContext.setAnnotateResult(opts.annotatedDescription); - CompletionContext.setIncludeObjectLiterals(opts.includeObjectLiterals); - CompletionContext.setAddInitsToTopLevel(opts.addInitsToTopLevel); - CompletionContext.setCallPatternHeuristics(opts.callPatternHeuristics); - std::unique_ptr callbacksFactory( - ide::makeCodeCompletionCallbacksFactory(CompletionContext, - SwiftConsumer)); - - SwiftConsumer.setContext(&CI.getASTContext(), &CI.getInvocation(), - &CompletionContext); - - auto *SF = CI.getCodeCompletionFile(); - performCodeCompletionSecondPass(*SF, *callbacksFactory); - SwiftConsumer.clearContext(); + const CodeCompletion::Options &opts, + llvm::function_ref)> + Callback) { + assert(Callback && "Must provide callback to receive results"); + + using ResultType = CancellableResult; + + /// Receive code completion results via \c handleResultsAndModules and calls + /// \c Callback with the corresponding values. + struct ConsumerToCallbackAdapter : public swift::ide::CodeCompletionConsumer { + CompilerInstance &CI; + ide::CodeCompletionContext &CompletionContext; + llvm::function_ref Callback; + bool HandleResultWasCalled = false; + + ConsumerToCallbackAdapter(CompilerInstance &CI, + ide::CodeCompletionContext &CompletionContext, + llvm::function_ref Callback) + : CI(CI), CompletionContext(CompletionContext), Callback(Callback) {} + + void + handleResultsAndModules(CodeCompletionContext &context, + ArrayRef requestedModules, + DeclContext *DC) override { + HandleResultWasCalled = true; + assert(&context == &CompletionContext); + Callback(ResultType::success({/*HasResults=*/true, &CI.getASTContext(), + &CI.getInvocation(), &CompletionContext, + requestedModules, DC})); + } + }; + + Lang.performCompletionLikeOperation( + UnresolvedInputFile, Offset, Args, FileSystem, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + // Create a factory for code completion callbacks that will feed the + // Consumer. + auto swiftCache = Lang.getCodeCompletionCache(); // Pin the cache. + ide::CodeCompletionContext CompletionContext(swiftCache->getCache()); + CompletionContext.ReusingASTContext = Result->DidReuseAST; + CompletionContext.setAnnotateResult(opts.annotatedDescription); + CompletionContext.setIncludeObjectLiterals( + opts.includeObjectLiterals); + CompletionContext.setAddInitsToTopLevel(opts.addInitsToTopLevel); + CompletionContext.setCallPatternHeuristics( + opts.callPatternHeuristics); + + CompilerInstance &CI = Result->CI; + ConsumerToCallbackAdapter Consumer(CI, CompletionContext, Callback); + + std::unique_ptr callbacksFactory( + ide::makeCodeCompletionCallbacksFactory(CompletionContext, + Consumer)); + + auto *SF = CI.getCodeCompletionFile(); + performCodeCompletionSecondPass(*SF, *callbacksFactory); + if (!Consumer.HandleResultWasCalled) { + // If we didn't receive a handleResult call from the second pass, + // we didn't receive any results. To make sure Callback gets called + // exactly once, call it manually with no results here. + Callback(ResultType::success( + {/*HasResults=*/false, &CI.getASTContext(), &CI.getInvocation(), + &CompletionContext, /*RequestedModules=*/{}, /*DC=*/nullptr})); + } + break; + } + case CancellableResultKind::Failure: + Callback(ResultType::failure(Result.getError())); + break; + case CancellableResultKind::Cancelled: + Callback(ResultType::cancelled()); + break; + } }); } @@ -216,11 +280,32 @@ void SwiftLangSupport::codeComplete( SKConsumer.setAnnotatedTypename(info.completionContext->getAnnotateResult()); }); - std::string Error; - if (!swiftCodeCompleteImpl(*this, UnresolvedInputFile, Offset, SwiftConsumer, - Args, fileSystem, CCOpts, Error)) { - SKConsumer.failed(Error); - } + swiftCodeCompleteImpl( + *this, UnresolvedInputFile, Offset, Args, fileSystem, CCOpts, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + if (Result->HasResults) { + SwiftConsumer.setContext(Result->Context, Result->Invocation, + Result->CompletionContext); + SwiftConsumer.handleResultsAndModules(*Result->CompletionContext, + Result->RequestedModules, + Result->DC); + SwiftConsumer.clearContext(); + // SwiftConsumer will deliver the results to SKConsumer. + } + // If we don't have any results, don't call anything on SwiftConsumer. + // This will cause us to deliver empty results. + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } + }); } static void getResultStructure( @@ -1031,11 +1116,44 @@ static void transformAndForwardResults( std::vector cargs; for (auto &arg : args) cargs.push_back(arg.c_str()); - std::string error; - if (!swiftCodeCompleteImpl(lang, buffer.get(), str.size(), swiftConsumer, - cargs, session->getFileSystem(), - options, error)) { - consumer.failed(error); + + bool CodeCompleteDidFail = false; + bool CallbackCalled = false; + + swiftCodeCompleteImpl( + lang, buffer.get(), str.size(), cargs, session->getFileSystem(), + options, [&](CancellableResult Result) { + CallbackCalled = true; + switch (Result.getKind()) { + case CancellableResultKind::Success: { + if (Result->HasResults) { + swiftConsumer.setContext(Result->Context, Result->Invocation, + Result->CompletionContext); + swiftConsumer.handleResultsAndModules(*Result->CompletionContext, + Result->RequestedModules, + Result->DC); + swiftConsumer.clearContext(); + } + // If we didn't receive any results, don't call any method on + // swiftConsumer. This will cause us to deliver empty results. + break; + } + case CancellableResultKind::Failure: + CodeCompleteDidFail = true; + consumer.failed(Result.getError()); + return; + case CancellableResultKind::Cancelled: + CodeCompleteDidFail = true; + consumer.cancelled(); + return; + } + }); + assert(CallbackCalled && + "Expected the callback to be called synchronously"); + + if (CodeCompleteDidFail) { + // We already informed the consumer that completion failed. No need to + // continue. return; } @@ -1085,12 +1203,13 @@ void SwiftLangSupport::codeCompleteOpen( translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset, maxResults); - std::string error; + std::string fileSystemError; // FIXME: the use of None as primary file is to match the fact we do not read // the document contents using the editor documents infrastructure. - auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); + auto fileSystem = + getFileSystem(vfsOptions, /*primaryFile=*/None, fileSystemError); if (!fileSystem) - return consumer.failed(error); + return consumer.failed(fileSystemError); CodeCompletion::FilterRules filterRules; translateFilterRules(rawFilterRules, filterRules); @@ -1123,10 +1242,42 @@ void SwiftLangSupport::codeCompleteOpen( extendCompletions(results, sink, info, nameToPopularity, CCOpts); }); + bool CodeCompleteDidFail = false; + bool CallbackCalled = false; // Invoke completion. - if (!swiftCodeCompleteImpl(*this, inputBuf, offset, swiftConsumer, - args, fileSystem, CCOpts, error)) { - consumer.failed(error); + swiftCodeCompleteImpl( + *this, inputBuf, offset, args, fileSystem, CCOpts, + [&](CancellableResult Result) { + CallbackCalled = true; + switch (Result.getKind()) { + case CancellableResultKind::Success: { + if (Result->HasResults) { + swiftConsumer.setContext(Result->Context, Result->Invocation, + Result->CompletionContext); + swiftConsumer.handleResultsAndModules(*Result->CompletionContext, + Result->RequestedModules, + Result->DC); + swiftConsumer.clearContext(); + } + // If we didn't receive any results, don't call any method on + // swiftConsumer. This will cause us to deliver empty results. + break; + } + case CancellableResultKind::Failure: + CodeCompleteDidFail = true; + consumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + CodeCompleteDidFail = true; + consumer.cancelled(); + break; + } + }); + assert(CallbackCalled && "Expected the callback to be called synchronously"); + + if (CodeCompleteDidFail) { + // We already informed the consumer that completion failed. No need to + // continue. return; } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp index 176f307dd14bd..3989d6f969d16 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp @@ -32,25 +32,73 @@ translateConformingMethodListOptions(OptionsDictionary &from, // ConformingMethodList doesn't receive any options at this point. } -static bool swiftConformingMethodListImpl( +/// The results returned from \c swiftConformingMethodListImpl through the +/// callback. +struct ConformingMethodListImplResult { + /// The actual results. If \c nullptr, no results were found. + const ide::ConformingMethodListResult *Result; + /// Whether an AST was reused for the completion. + bool DidReuseAST; +}; + +static void swiftConformingMethodListImpl( SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, ArrayRef Args, ArrayRef ExpectedTypeNames, - ide::ConformingMethodListConsumer &Consumer, llvm::IntrusiveRefCntPtr FileSystem, - std::string &Error) { - return Lang.performCompletionLikeOperation( - UnresolvedInputFile, Offset, Args, FileSystem, Error, - [&](CompilerInstance &CI, bool reusingASTContext) { - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames, - Consumer)); - - auto *SF = CI.getCodeCompletionFile(); - performCodeCompletionSecondPass(*SF, *callbacksFactory); - Consumer.setReusingASTContext(reusingASTContext); + llvm::function_ref)> + Callback) { + assert(Callback && "Must provide callback to receive results"); + + using ResultType = CancellableResult; + + struct ConsumerToCallbackAdapter + : public swift::ide::ConformingMethodListConsumer { + bool ReusingASTContext; + llvm::function_ref Callback; + bool HandleResultWasCalled = false; + + ConsumerToCallbackAdapter(bool ReusingASTContext, + llvm::function_ref Callback) + : ReusingASTContext(ReusingASTContext), Callback(Callback) {} + + void handleResult(const ide::ConformingMethodListResult &result) override { + HandleResultWasCalled = true; + Callback(ResultType::success({&result, ReusingASTContext})); + } + }; + + Lang.performCompletionLikeOperation( + UnresolvedInputFile, Offset, Args, FileSystem, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + ConsumerToCallbackAdapter Consumer(Result->DidReuseAST, Callback); + + // Create a factory for code completion callbacks that will feed the + // Consumer. + std::unique_ptr callbacksFactory( + ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames, + Consumer)); + + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), + *callbacksFactory); + if (!Consumer.HandleResultWasCalled) { + // If we didn't receive a handleResult call from the second pass, + // we didn't receive any results. To make sure Callback gets called + // exactly once, call it manually with no results here. + Callback(ResultType::success( + {/*Results=*/nullptr, Result->DidReuseAST})); + } + break; + } + case CancellableResultKind::Failure: + Callback(ResultType::failure(Result.getError())); + break; + case CancellableResultKind::Cancelled: + Callback(ResultType::cancelled()); + break; + } }); } @@ -186,14 +234,30 @@ void SwiftLangSupport::getConformingMethodList( SKConsumer.handleResult(SKResult); } - void setReusingASTContext(bool flag) override { + void setReusingASTContext(bool flag) { SKConsumer.setReusingASTContext(flag); } } Consumer(SKConsumer); - if (!swiftConformingMethodListImpl(*this, UnresolvedInputFile, Offset, Args, - ExpectedTypeNames, Consumer, fileSystem, - error)) { - SKConsumer.failed(error); - } + swiftConformingMethodListImpl( + *this, UnresolvedInputFile, Offset, Args, ExpectedTypeNames, fileSystem, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + if (Result->Result) { + Consumer.handleResult(*Result->Result); + } + // If we didn't receive any result, don't call handleResult on + // Consumer to deliver empty results. + Consumer.setReusingASTContext(Result->DidReuseAST); + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } + }); } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index fe1233b769f58..7874fb1e44c36 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -1000,12 +1000,12 @@ SwiftLangSupport::getFileSystem(const Optional &vfsOptions, return llvm::vfs::getRealFileSystem(); } -bool SwiftLangSupport::performCompletionLikeOperation( +void SwiftLangSupport::performCompletionLikeOperation( llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, - std::string &Error, - llvm::function_ref Callback) { + llvm::function_ref)> + Callback) { assert(FileSystem); // Resolve symlinks for the input file; we resolve them for the input files @@ -1045,22 +1045,26 @@ bool SwiftLangSupport::performCompletionLikeOperation( ForwardingDiagnosticConsumer CIDiags(Diags); CompilerInvocation Invocation; - bool Failed = getASTManager()->initCompilerInvocation( + std::string CompilerInvocationError; + bool CreatingInvocationFailed = getASTManager()->initCompilerInvocation( Invocation, Args, Diags, newBuffer->getBufferIdentifier(), FileSystem, - Error); - if (Failed) - return false; + CompilerInvocationError); + if (CreatingInvocationFailed) { + Callback(CancellableResult::failure( + CompilerInvocationError)); + return; + } if (!Invocation.getFrontendOptions().InputsAndOutputs.hasInputs()) { - Error = "no input filenames specified"; - return false; + Callback(CancellableResult::failure( + "no input filenames specified")); + return; } // Pin completion instance. auto CompletionInst = getCompletionInstance(); - return CompletionInst->performOperation(Invocation, Args, FileSystem, - newBuffer.get(), Offset, - Error, &CIDiags, Callback); + CompletionInst->performOperation(Invocation, Args, FileSystem, + newBuffer.get(), Offset, &CIDiags, Callback); } CloseClangModuleFiles::~CloseClangModuleFiles() { diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index 8a7b4ebd8548d..02bb4e02f1549 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -14,13 +14,15 @@ #define LLVM_SOURCEKIT_LIB_SWIFTLANG_SWIFTLANGSUPPORT_H #include "CodeCompletion.h" -#include "SwiftInterfaceGenContext.h" #include "SourceKit/Core/LangSupport.h" #include "SourceKit/Support/Concurrency.h" #include "SourceKit/Support/Statistic.h" #include "SourceKit/Support/ThreadSafeRefCntPtr.h" #include "SourceKit/Support/Tracing.h" +#include "SwiftInterfaceGenContext.h" #include "swift/Basic/ThreadSafeRefCounted.h" +#include "swift/IDE/CancellableResult.h" +#include "swift/IDE/CompletionInstance.h" #include "swift/IDE/Indenting.h" #include "swift/IDE/Refactoring.h" #include "swift/Index/IndexSymbol.h" @@ -462,12 +464,13 @@ class SwiftLangSupport : public LangSupport { /// Perform a completion like operation. It initializes a \c CompilerInstance, /// the calls \p Callback with it. \p Callback must perform the second pass /// using that instance. - bool performCompletionLikeOperation( + void performCompletionLikeOperation( llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, - std::string &Error, - llvm::function_ref Callback); + llvm::function_ref)> + Callback); //==========================================================================// // LangSupport Interface diff --git a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp index 9e857ba8ac506..74b029649a829 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp @@ -30,23 +30,69 @@ static void translateTypeContextInfoOptions(OptionsDictionary &from, // TypeContextInfo doesn't receive any options at this point. } -static bool swiftTypeContextInfoImpl( +/// The results returned from \c swiftTypeContextInfoImpl via \c Callback. +struct SwiftTypeContextInfoImplResult { + /// The actual results. If empty, no results were found. + ArrayRef Results; + /// Whether an AST was reused to produce the results. + bool DidReuseAST; +}; + +static void swiftTypeContextInfoImpl( SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, - unsigned Offset, ide::TypeContextInfoConsumer &Consumer, - ArrayRef Args, + unsigned Offset, ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, - std::string &Error) { - return Lang.performCompletionLikeOperation( - UnresolvedInputFile, Offset, Args, FileSystem, Error, - [&](CompilerInstance &CI, bool reusingASTContext) { - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeTypeContextInfoCallbacksFactory(Consumer)); - - auto *SF = CI.getCodeCompletionFile(); - performCodeCompletionSecondPass(*SF, *callbacksFactory); - Consumer.setReusingASTContext(reusingASTContext); + llvm::function_ref)> + Callback) { + assert(Callback && "Must provide callback to receive results"); + + using ResultType = CancellableResult; + + struct ConsumerToCallbackAdapter : public ide::TypeContextInfoConsumer { + bool ReusingASTContext; + llvm::function_ref Callback; + bool HandleResultsCalled = false; + + ConsumerToCallbackAdapter(bool ReusingASTContext, + llvm::function_ref Callback) + : ReusingASTContext(ReusingASTContext), Callback(Callback) {} + + void handleResults(ArrayRef Results) override { + HandleResultsCalled = true; + Callback(ResultType::success({Results, ReusingASTContext})); + } + }; + + Lang.performCompletionLikeOperation( + UnresolvedInputFile, Offset, Args, FileSystem, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + ConsumerToCallbackAdapter Consumer(Result->DidReuseAST, Callback); + + // Create a factory for code completion callbacks that will feed the + // Consumer. + std::unique_ptr callbacksFactory( + ide::makeTypeContextInfoCallbacksFactory(Consumer)); + + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), + *callbacksFactory); + if (!Consumer.HandleResultsCalled) { + // If we didn't receive a handleResult call from the second pass, + // we didn't receive any results. To make sure Callback gets called + // exactly once, call it manually with no results here. + Callback( + ResultType::success({/*Results=*/{}, Result->DidReuseAST})); + } + break; + } + case CancellableResultKind::Failure: + Callback(ResultType::failure(Result.getError())); + break; + case CancellableResultKind::Cancelled: + Callback(ResultType::cancelled()); + break; + } }); } @@ -160,13 +206,26 @@ void SwiftLangSupport::getExpressionContextInfo( handleSingleResult(Item); } - void setReusingASTContext(bool flag) override { + void setReusingASTContext(bool flag) { SKConsumer.setReusingASTContext(flag); } } Consumer(SKConsumer); - if (!swiftTypeContextInfoImpl(*this, UnresolvedInputFile, Offset, Consumer, - Args, fileSystem, error)) { - SKConsumer.failed(error); - } + swiftTypeContextInfoImpl( + *this, UnresolvedInputFile, Offset, Args, fileSystem, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + Consumer.handleResults(Result->Results); + Consumer.setReusingASTContext(Result->DidReuseAST); + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } + }); } diff --git a/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp b/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp index ed412a0e6747d..61b0c7d97c9c5 100644 --- a/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp +++ b/tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp @@ -2136,6 +2136,7 @@ class SKCodeCompletionConsumer : public CodeCompletionConsumer { CodeCompletionResultsArrayBuilder ResultsBuilder; std::string ErrorDescription; + bool WasCancelled = false; public: explicit SKCodeCompletionConsumer(ResponseBuilder &RespBuilder) @@ -2143,16 +2144,20 @@ class SKCodeCompletionConsumer : public CodeCompletionConsumer { } sourcekitd_response_t createResponse() { - if (!ErrorDescription.empty()) + if (WasCancelled) { + return createErrorRequestCancelled(); + } else if (!ErrorDescription.empty()) { return createErrorRequestFailed(ErrorDescription.c_str()); - - RespBuilder.getDictionary().setCustomBuffer(KeyResults, - ResultsBuilder.createBuffer()); - return RespBuilder.createResponse(); + } else { + RespBuilder.getDictionary().setCustomBuffer( + KeyResults, ResultsBuilder.createBuffer()); + return RespBuilder.createResponse(); + } } void failed(StringRef ErrDescription) override; + void cancelled() override; void setCompletionKind(UIdent kind) override; void setReusingASTContext(bool flag) override; @@ -2183,6 +2188,8 @@ void SKCodeCompletionConsumer::failed(StringRef ErrDescription) { ErrorDescription = ErrDescription.str(); } +void SKCodeCompletionConsumer::cancelled() { WasCancelled = true; } + void SKCodeCompletionConsumer::setCompletionKind(UIdent kind) { assert(kind.isValid()); RespBuilder.getDictionary().set(KeyKind, kind); @@ -2238,19 +2245,25 @@ class SKGroupedCodeCompletionConsumer : public GroupedCodeCompletionConsumer { ResponseBuilder::Dictionary Response; SmallVector GroupContentsStack; std::string ErrorDescription; + bool WasCancelled = false; public: explicit SKGroupedCodeCompletionConsumer(ResponseBuilder &RespBuilder) : RespBuilder(RespBuilder) {} sourcekitd_response_t createResponse() { - if (!ErrorDescription.empty()) + if (WasCancelled) { + return createErrorRequestCancelled(); + } else if (!ErrorDescription.empty()) { return createErrorRequestFailed(ErrorDescription.c_str()); - assert(GroupContentsStack.empty() && "mismatched start/endGroup"); - return RespBuilder.createResponse(); + } else { + assert(GroupContentsStack.empty() && "mismatched start/endGroup"); + return RespBuilder.createResponse(); + } } void failed(StringRef ErrDescription) override; + void cancelled() override; bool handleResult(const CodeCompletionInfo &Info) override; void startGroup(UIdent kind, StringRef name) override; void endGroup() override; @@ -2376,6 +2389,8 @@ void SKGroupedCodeCompletionConsumer::failed(StringRef ErrDescription) { ErrorDescription = ErrDescription.str(); } +void SKGroupedCodeCompletionConsumer::cancelled() { WasCancelled = true; } + bool SKGroupedCodeCompletionConsumer::handleResult(const CodeCompletionInfo &R) { assert(!GroupContentsStack.empty() && "missing root group"); @@ -2478,6 +2493,7 @@ static sourcekitd_response_t typeContextInfo(llvm::MemoryBuffer *InputBuf, ResponseBuilder RespBuilder; ResponseBuilder::Array SKResults; Optional ErrorDescription; + bool WasCancelled = false; public: Consumer(ResponseBuilder Builder) @@ -2508,6 +2524,9 @@ static sourcekitd_response_t typeContextInfo(llvm::MemoryBuffer *InputBuf, ErrorDescription = ErrDescription.str(); } + void cancelled() override { WasCancelled = true; } + + bool wasCancelled() const { return WasCancelled; } bool isError() const { return ErrorDescription.hasValue(); } const char *getErrorDescription() const { return ErrorDescription->c_str(); @@ -2522,9 +2541,13 @@ static sourcekitd_response_t typeContextInfo(llvm::MemoryBuffer *InputBuf, Lang.getExpressionContextInfo(InputBuf, Offset, options.get(), Args, Consumer, std::move(vfsOptions)); - if (Consumer.isError()) + if (Consumer.wasCancelled()) { + return createErrorRequestCancelled(); + } else if (Consumer.isError()) { return createErrorRequestFailed(Consumer.getErrorDescription()); - return RespBuilder.createResponse(); + } else { + return RespBuilder.createResponse(); + } } //===----------------------------------------------------------------------===// @@ -2542,6 +2565,7 @@ conformingMethodList(llvm::MemoryBuffer *InputBuf, int64_t Offset, class Consumer : public ConformingMethodListConsumer { ResponseBuilder::Dictionary SKResult; Optional ErrorDescription; + bool WasCancelled = false; public: Consumer(ResponseBuilder Builder) : SKResult(Builder.getDictionary()) {} @@ -2571,6 +2595,9 @@ conformingMethodList(llvm::MemoryBuffer *InputBuf, int64_t Offset, ErrorDescription = ErrDescription.str(); } + void cancelled() override { WasCancelled = true; } + + bool wasCancelled() const { return WasCancelled; } bool isError() const { return ErrorDescription.hasValue(); } const char *getErrorDescription() const { return ErrorDescription->c_str(); @@ -2585,9 +2612,13 @@ conformingMethodList(llvm::MemoryBuffer *InputBuf, int64_t Offset, Lang.getConformingMethodList(InputBuf, Offset, options.get(), Args, ExpectedTypes, Consumer, std::move(vfsOptions)); - if (Consumer.isError()) + if (Consumer.wasCancelled()) { + return createErrorRequestCancelled(); + } else if (Consumer.isError()) { return createErrorRequestFailed(Consumer.getErrorDescription()); - return RespBuilder.createResponse(); + } else { + return RespBuilder.createResponse(); + } } //===----------------------------------------------------------------------===// diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 41247167848e6..26435b65491b0 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -840,7 +840,7 @@ static bool doCodeCompletionImpl( bool CodeCompletionDiagnostics) { std::unique_ptr FileBuf; if (setBufferForFile(SourceFilename, FileBuf)) - return 1; + return true; unsigned Offset; @@ -850,7 +850,7 @@ static bool doCodeCompletionImpl( if (Offset == ~0U) { llvm::errs() << "could not find code completion token \"" << CodeCompletionToken << "\"\n"; - return 1; + return true; } llvm::outs() << "found code completion token " << CodeCompletionToken << " at offset " << Offset << "\n"; @@ -864,19 +864,30 @@ static bool doCodeCompletionImpl( SecondSourceFileName); } - std::string Error; PrintingDiagnosticConsumer PrintDiags; CompletionInstance CompletionInst; - auto isSuccess = CompletionInst.performOperation( + // FIXME: Should we count it as an error if we didn't receive a callback? + bool HadError = false; + CompletionInst.performOperation( Invocation, /*Args=*/{}, llvm::vfs::getRealFileSystem(), CleanFile.get(), - Offset, Error, - CodeCompletionDiagnostics ? &PrintDiags : nullptr, - [&](CompilerInstance &CI, bool reusingASTContext) { - assert(!reusingASTContext && "reusing AST context without enabling it"); - auto *SF = CI.getCodeCompletionFile(); - performCodeCompletionSecondPass(*SF, *callbacksFactory); + Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + HadError = false; + assert(!Result->DidReuseAST && + "reusing AST context without enabling it"); + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), + *callbacksFactory); + break; + } + case CancellableResultKind::Failure: + case CancellableResultKind::Cancelled: + HadError = true; + break; + } }); - return isSuccess ? 0 : 1; + return HadError; } static int doTypeContextInfo(const CompilerInvocation &InitInvok, @@ -1268,30 +1279,49 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, PrintingDiagnosticConsumer PrintDiags; auto completionStart = std::chrono::high_resolution_clock::now(); bool wasASTContextReused = false; - bool isSuccess = CompletionInst.performOperation( + // FIXME: Should we count it as an error if we didn't receive a callback? + bool completionDidFail = false; + CompletionInst.performOperation( Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset, - Error, CodeCompletionDiagnostics ? &PrintDiags : nullptr, - [&](CompilerInstance &CI, bool reusingASTContext) { - // Create a CodeCompletionConsumer. - std::unique_ptr Consumer( - new ide::PrintingCodeCompletionConsumer(OS, IncludeKeywords, - IncludeComments, - CodeCompletionAnnotateResults, - IncludeSourceText)); - - // Create a factory for code completion callbacks that will feed the - // Consumer. - ide::CodeCompletionContext CompletionContext(CompletionCache); - CompletionContext.setAnnotateResult(CodeCompletionAnnotateResults); - CompletionContext.setAddInitsToTopLevel(CodeCompletionAddInitsToTopLevel); - CompletionContext.setCallPatternHeuristics(CodeCompletionCallPatternHeuristics); - std::unique_ptr callbacksFactory( - ide::makeCodeCompletionCallbacksFactory(CompletionContext, - *Consumer)); - - auto *SF = CI.getCodeCompletionFile(); - performCodeCompletionSecondPass(*SF, *callbacksFactory); - wasASTContextReused = reusingASTContext; + CodeCompletionDiagnostics ? &PrintDiags : nullptr, + [&](CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + completionDidFail = false; + + wasASTContextReused = Result->DidReuseAST; + + // Create a CodeCompletionConsumer. + std::unique_ptr Consumer( + new ide::PrintingCodeCompletionConsumer( + OS, IncludeKeywords, IncludeComments, + CodeCompletionAnnotateResults, IncludeSourceText)); + + // Create a factory for code completion callbacks that will feed the + // Consumer. + ide::CodeCompletionContext CompletionContext(CompletionCache); + CompletionContext.setAnnotateResult(CodeCompletionAnnotateResults); + CompletionContext.setAddInitsToTopLevel( + CodeCompletionAddInitsToTopLevel); + CompletionContext.setCallPatternHeuristics( + CodeCompletionCallPatternHeuristics); + std::unique_ptr callbacksFactory( + ide::makeCodeCompletionCallbacksFactory(CompletionContext, + *Consumer)); + + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), + *callbacksFactory); + break; + } + case CancellableResultKind::Failure: + completionDidFail = true; + llvm::errs() << "error: " << Result.getError() << "\n"; + break; + case CancellableResultKind::Cancelled: + completionDidFail = true; + llvm::errs() << "request cancelled\n"; + break; + } }); auto completionEnd = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration_cast( @@ -1323,8 +1353,8 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, fileOut << ResultStr; fileOut.close(); - if (!isSuccess) { - llvm::errs() << "error: " << Error << "\n"; + if (completionDidFail) { + // error message was already emitted above. return 1; } From b6e03e3d985fc5ee0b86d145012a96f7b5fa9816 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 7 Oct 2021 19:04:56 +0200 Subject: [PATCH 02/12] [CodeCompletion] Make sure callback is always called from performOperation We had some situations left that neither returned an error, nor called the callback with results in `performOperation`. Return an error in these and adjust the tests to correctly match the error. --- include/swift/IDE/CancellableResult.h | 2 +- include/swift/IDE/CompletionInstance.h | 4 ++ lib/IDE/CompletionInstance.cpp | 21 +++--- .../complete_without_stdlib.swift | 11 ++-- .../lib/SwiftLang/SwiftCompletion.cpp | 11 +++- .../SwiftLang/SwiftConformingMethodList.cpp | 6 ++ .../lib/SwiftLang/SwiftTypeContextInfo.cpp | 5 ++ tools/swift-ide-test/swift-ide-test.cpp | 65 ++++++++++++++----- 8 files changed, 89 insertions(+), 36 deletions(-) diff --git a/include/swift/IDE/CancellableResult.h b/include/swift/IDE/CancellableResult.h index 5ef6177df3b95..c293089aaeef6 100644 --- a/include/swift/IDE/CancellableResult.h +++ b/include/swift/IDE/CancellableResult.h @@ -78,7 +78,7 @@ class CancellableResult { ResultType(std::move(Other.Result)); break; case CancellableResultKind::Failure: - Error = std::move(Other.Error); + ::new ((void *)std::addressof(Error)) std::string(std::move(Other.Error)); break; case CancellableResultKind::Cancelled: break; diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index b5f2d9786bc23..31335a97ac41e 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -42,6 +42,10 @@ struct CompletionInstanceResult { CompilerInstance &CI; /// Whether an AST was reused. bool DidReuseAST; + /// Whether the CompletionInstance found a code completion token in the source + /// file. If this is \c false, the user will most likely want to return empty + /// results. + bool DidFindCodeCompletionToken; }; /// Manages \c CompilerInstance for completion like operations. diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index 4a782d4926d81..6a1fd76ae6c26 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -506,7 +506,7 @@ bool CompletionInstance::performCachedOperationIfPossible( CI.addDiagnosticConsumer(DiagC); Callback(CancellableResult::success( - {CI, /*reusingASTContext=*/true})); + {CI, /*reusingASTContext=*/true, /*DidFindCodeCompletionToken=*/true})); if (DiagC) CI.removeDiagnosticConsumer(DiagC); @@ -535,6 +535,7 @@ void CompletionInstance::performNewOperation( Invocation.getFrontendOptions().IntermoduleDependencyTracking = IntermoduleDepTrackingMode::ExcludeSystem; + bool DidFindCodeCompletionToken = false; { auto &CI = *TheInstance; if (DiagC) @@ -561,24 +562,26 @@ void CompletionInstance::performNewOperation( // failed to load, let's bail early and hand back an empty completion // result to avoid any downstream crashes. if (CI.loadStdlibIfNeeded()) { - /// FIXME: Callback is not being called on this path. + Callback(CancellableResult::failure( + "failed to load the standard library")); return; } CI.performParseAndResolveImportsOnly(); - // If we didn't find a code completion token, bail. - auto *state = CI.getCodeCompletionFile()->getDelayedParserState(); - if (!state->hasCodeCompletionDelayedDeclState()) - /// FIXME: Callback is not being called on this path. - return; + DidFindCodeCompletionToken = CI.getCodeCompletionFile() + ->getDelayedParserState() + ->hasCodeCompletionDelayedDeclState(); Callback(CancellableResult::success( - {CI, /*ReuisingASTContext=*/false})); + {CI, /*ReuisingASTContext=*/false, DidFindCodeCompletionToken})); } // Cache the compiler instance if fast completion is enabled. - if (isCachedCompletionRequested) + // If we didn't find a code compleiton token, we can't cache the instance + // because performCachedOperationIfPossible wouldn't have an old code + // completion state to compare the new one to. + if (isCachedCompletionRequested && DidFindCodeCompletionToken) cacheCompilerInstance(std::move(TheInstance), *ArgsHash); } diff --git a/test/SourceKit/CodeComplete/complete_without_stdlib.swift b/test/SourceKit/CodeComplete/complete_without_stdlib.swift index 7ece49f8f6aa4..09e2a5851761a 100644 --- a/test/SourceKit/CodeComplete/complete_without_stdlib.swift +++ b/test/SourceKit/CodeComplete/complete_without_stdlib.swift @@ -9,12 +9,11 @@ class Str { // RUN: %empty-directory(%t/rsrc) // RUN: %empty-directory(%t/sdk) -// RUN: %sourcekitd-test \ +// RUN: not %sourcekitd-test \ // RUN: -req=global-config -req-opts=completion_max_astcontext_reuse_count=0 \ -// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk | %FileCheck %s -// RUN: %sourcekitd-test \ +// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk 2>&1 | %FileCheck %s +// RUN: not %sourcekitd-test \ // RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk == \ -// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk | %FileCheck %s +// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk 2>&1 | %FileCheck %s -// CHECK: key.results: [ -// CHECK-NOT: key.description: +// CHECK: error response (Request Failed): failed to load the standard library diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp index 47e28801aaaa9..5027831632802 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp @@ -190,8 +190,15 @@ static void swiftCodeCompleteImpl( ide::makeCodeCompletionCallbacksFactory(CompletionContext, Consumer)); - auto *SF = CI.getCodeCompletionFile(); - performCodeCompletionSecondPass(*SF, *callbacksFactory); + if (!Result->DidFindCodeCompletionToken) { + Callback(ResultType::success( + {/*HasResults=*/false, &CI.getASTContext(), &CI.getInvocation(), + &CompletionContext, /*RequestedModules=*/{}, /*DC=*/nullptr})); + return; + } + + performCodeCompletionSecondPass(*CI.getCodeCompletionFile(), + *callbacksFactory); if (!Consumer.HandleResultWasCalled) { // If we didn't receive a handleResult call from the second pass, // we didn't receive any results. To make sure Callback gets called diff --git a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp index 3989d6f969d16..44c26459406f1 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp @@ -81,6 +81,12 @@ static void swiftConformingMethodListImpl( ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames, Consumer)); + if (!Result->DidFindCodeCompletionToken) { + Callback(ResultType::success( + {/*Results=*/nullptr, Result->DidReuseAST})); + return; + } + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), *callbacksFactory); if (!Consumer.HandleResultWasCalled) { diff --git a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp index 74b029649a829..bc080157c2cb9 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp @@ -75,6 +75,11 @@ static void swiftTypeContextInfoImpl( std::unique_ptr callbacksFactory( ide::makeTypeContextInfoCallbacksFactory(Consumer)); + if (!Result->DidFindCodeCompletionToken) { + Callback( + ResultType::success({/*Results=*/{}, Result->DidReuseAST})); + } + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), *callbacksFactory); if (!Consumer.HandleResultsCalled) { diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 26435b65491b0..21483f8fb44f9 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -866,28 +866,48 @@ static bool doCodeCompletionImpl( PrintingDiagnosticConsumer PrintDiags; CompletionInstance CompletionInst; - // FIXME: Should we count it as an error if we didn't receive a callback? - bool HadError = false; + std::string CompletionError; + bool CallbackCalled = false; CompletionInst.performOperation( Invocation, /*Args=*/{}, llvm::vfs::getRealFileSystem(), CleanFile.get(), Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr, [&](CancellableResult Result) { + CallbackCalled = true; switch (Result.getKind()) { case CancellableResultKind::Success: { - HadError = false; assert(!Result->DidReuseAST && "reusing AST context without enabling it"); + + if (!Result->DidFindCodeCompletionToken) { + // Return empty results by not performing the second pass and never + // calling the consumer of the callback factory. + return; + } + performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), *callbacksFactory); break; } case CancellableResultKind::Failure: + CompletionError = "error: " + Result.getError(); + break; case CancellableResultKind::Cancelled: - HadError = true; + CompletionError = "request cancelled"; break; } }); - return HadError; + assert(CallbackCalled && + "We should always receive a callback call for code completion"); + if (CompletionError == "error: did not find code completion token") { + // Do not consider failure to find the code completion token as a critical + // failure that returns a non-zero exit code. Instead, allow the caller to + // match the error message. + llvm::outs() << CompletionError << '\n'; + } else if (!CompletionError.empty()) { + llvm::errs() << CompletionError << '\n'; + return true; + } + return false; } static int doTypeContextInfo(const CompilerInvocation &InitInvok, @@ -1279,16 +1299,20 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, PrintingDiagnosticConsumer PrintDiags; auto completionStart = std::chrono::high_resolution_clock::now(); bool wasASTContextReused = false; - // FIXME: Should we count it as an error if we didn't receive a callback? - bool completionDidFail = false; + std::string completionError = ""; + bool CallbackCalled = false; CompletionInst.performOperation( Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr, [&](CancellableResult Result) { + CallbackCalled = true; switch (Result.getKind()) { case CancellableResultKind::Success: { - completionDidFail = false; - + if (!Result->DidFindCodeCompletionToken) { + // Return empty results by not performing the second pass and + // never calling the consumer of the callback factory. + return; + } wasASTContextReused = Result->DidReuseAST; // Create a CodeCompletionConsumer. @@ -1314,15 +1338,15 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, break; } case CancellableResultKind::Failure: - completionDidFail = true; - llvm::errs() << "error: " << Result.getError() << "\n"; + completionError = "error: " + Result.getError(); break; case CancellableResultKind::Cancelled: - completionDidFail = true; - llvm::errs() << "request cancelled\n"; + completionError = "request cancelled"; break; } }); + assert(CallbackCalled && + "We should always receive a callback call for code completion"); auto completionEnd = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration_cast( completionEnd - completionStart); @@ -1350,13 +1374,18 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, } llvm::raw_fd_ostream fileOut(resultFD, /*shouldClose=*/true); - fileOut << ResultStr; - fileOut.close(); - - if (completionDidFail) { - // error message was already emitted above. + if (completionError == "error: did not find code completion token") { + // Do not consider failure to find the code completion token as a critical + // failure that returns a non-zero exit code. Instead, allow the caller to + // match the error message. + fileOut << completionError; + } else if (!completionError.empty()) { + llvm::errs() << completionError << '\n'; return 1; + } else { + fileOut << ResultStr; } + fileOut.close(); if (options::SkipFileCheck) continue; From ab257bbda3205551e46b4df06f65d9c6165c0545 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 12:39:59 +0200 Subject: [PATCH 03/12] [SourceKit] Move invocation of code completion second pass for TypeContextInfo from SoruceKit to CompletionInstance The invocation of the code completion second pass should be implementation detail of `CompletionInstance`. Create a method on `CompletionInstance` that correctly invokes the second pass and just reutnrs the type context info results to the caller. --- include/swift/IDE/CancellableResult.h | 30 ++++ include/swift/IDE/CompletionInstance.h | 17 ++ lib/IDE/CompletionInstance.cpp | 56 ++++++ .../lib/SwiftLang/SwiftLangSupport.cpp | 36 +++- .../lib/SwiftLang/SwiftLangSupport.h | 18 ++ .../lib/SwiftLang/SwiftTypeContextInfo.cpp | 165 +++++------------- 6 files changed, 194 insertions(+), 128 deletions(-) diff --git a/include/swift/IDE/CancellableResult.h b/include/swift/IDE/CancellableResult.h index c293089aaeef6..db6a79da18117 100644 --- a/include/swift/IDE/CancellableResult.h +++ b/include/swift/IDE/CancellableResult.h @@ -140,6 +140,36 @@ class CancellableResult { assert(getKind() == CancellableResultKind::Failure); return Error; } + + /// If the result represents success, invoke \p Transform to asynchronously + /// transform the wrapped result type and produce a new result type that is + /// provided by the callback function passed to \p Transform. Afterwards call + /// \p Handle with either the transformed value or the failure or cancelled + /// result. + /// The \c async part of the map means that the transform might happen + /// asyncronously. This function does not introduce asynchronicity by itself. + /// \p Transform might also invoke the callback synchronously. + template + void + mapAsync(llvm::function_ref< + void(ResultType &, + llvm::function_ref)>)> + Transform, + llvm::function_ref)> Handle) { + switch (getKind()) { + case CancellableResultKind::Success: + Transform(getResult(), [&](CancellableResult NewResult) { + Handle(NewResult); + }); + break; + case CancellableResultKind::Failure: + Handle(CancellableResult::failure(getError())); + break; + case CancellableResultKind::Cancelled: + Handle(CancellableResult::cancelled()); + break; + } + } }; } // namespace ide diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index 31335a97ac41e..ba4097089cd23 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -15,6 +15,7 @@ #include "swift/Frontend/Frontend.h" #include "swift/IDE/CancellableResult.h" +#include "swift/IDE/TypeContextInfo.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringRef.h" @@ -48,6 +49,14 @@ struct CompletionInstanceResult { bool DidFindCodeCompletionToken; }; +/// The results returned from \c CompletionInstance::typeContextInfo. +struct TypeContextInfoResult { + /// The actual results. If empty, no results were found. + ArrayRef Results; + /// Whether an AST was reused to produce the results. + bool DidReuseAST; +}; + /// Manages \c CompilerInstance for completion like operations. class CompletionInstance { struct Options { @@ -124,6 +133,14 @@ class CompletionInstance { DiagnosticConsumer *DiagC, llvm::function_ref)> Callback); + + void typeContextInfo( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *completionBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, + llvm::function_ref)> + Callback); }; } // namespace ide diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index 6a1fd76ae6c26..ce96cf5a07e97 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -655,3 +655,59 @@ void swift::ide::CompletionInstance::performOperation( performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer, Offset, DiagC, Callback); } + +void swift::ide::CompletionInstance::typeContextInfo( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *completionBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, + llvm::function_ref)> + Callback) { + using ResultType = CancellableResult; + + struct ConsumerToCallbackAdapter : public ide::TypeContextInfoConsumer { + bool ReusingASTContext; + llvm::function_ref Callback; + bool HandleResultsCalled = false; + + ConsumerToCallbackAdapter(bool ReusingASTContext, + llvm::function_ref Callback) + : ReusingASTContext(ReusingASTContext), Callback(Callback) {} + + void handleResults(ArrayRef Results) override { + HandleResultsCalled = true; + Callback(ResultType::success({Results, ReusingASTContext})); + } + }; + + performOperation( + Invocation, Args, FileSystem, completionBuffer, Offset, DiagC, + [&](CancellableResult CIResult) { + CIResult.mapAsync( + [](auto &Result, auto DeliverTransformed) { + ConsumerToCallbackAdapter Consumer(Result.DidReuseAST, + DeliverTransformed); + std::unique_ptr callbacksFactory( + ide::makeTypeContextInfoCallbacksFactory(Consumer)); + + if (!Result.DidFindCodeCompletionToken) { + // Deliver empty results if we didn't find a code completion + // token. + DeliverTransformed( + ResultType::success({/*Results=*/{}, Result.DidReuseAST})); + } + + performCodeCompletionSecondPass( + *Result.CI.getCodeCompletionFile(), *callbacksFactory); + if (!Consumer.HandleResultsCalled) { + // If we didn't receive a handleResult call from the second + // pass, we didn't receive any results. To make sure + // DeliverTransformed gets called exactly once, call it with + // no results here. + DeliverTransformed( + ResultType::success({/*Results=*/{}, Result.DidReuseAST})); + } + }, + Callback); + }); +} diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index 7874fb1e44c36..2593e12a3b184 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -1000,12 +1000,12 @@ SwiftLangSupport::getFileSystem(const Optional &vfsOptions, return llvm::vfs::getRealFileSystem(); } -void SwiftLangSupport::performCompletionLikeOperation( +void SwiftLangSupport::performWithParamsToCompletionLikeOperation( llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, - llvm::function_ref)> - Callback) { + llvm::function_ref)> + PerformOperation) { assert(FileSystem); // Resolve symlinks for the input file; we resolve them for the input files @@ -1050,12 +1050,12 @@ void SwiftLangSupport::performCompletionLikeOperation( Invocation, Args, Diags, newBuffer->getBufferIdentifier(), FileSystem, CompilerInvocationError); if (CreatingInvocationFailed) { - Callback(CancellableResult::failure( + PerformOperation(CancellableResult::failure( CompilerInvocationError)); return; } if (!Invocation.getFrontendOptions().InputsAndOutputs.hasInputs()) { - Callback(CancellableResult::failure( + PerformOperation(CancellableResult::failure( "no input filenames specified")); return; } @@ -1063,8 +1063,30 @@ void SwiftLangSupport::performCompletionLikeOperation( // Pin completion instance. auto CompletionInst = getCompletionInstance(); - CompletionInst->performOperation(Invocation, Args, FileSystem, - newBuffer.get(), Offset, &CIDiags, Callback); + CompletionLikeOperationParams Params = {Invocation, newBuffer.get(), + &CIDiags}; + PerformOperation( + CancellableResult::success(Params)); +} + +void SwiftLangSupport::performCompletionLikeOperation( + llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, + ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::function_ref)> + Callback) { + performWithParamsToCompletionLikeOperation( + UnresolvedInputFile, Offset, Args, FileSystem, + [&](CancellableResult ParamsResult) { + ParamsResult.mapAsync( + [&](auto &CIParams, auto DeliverTransformed) { + getCompletionInstance()->performOperation( + CIParams.Invocation, Args, FileSystem, + CIParams.completionBuffer, Offset, CIParams.DiagC, + DeliverTransformed); + }, + Callback); + }); } CloseClangModuleFiles::~CloseClangModuleFiles() { diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index 02bb4e02f1549..dcaacd4ef2c33 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -20,6 +20,7 @@ #include "SourceKit/Support/ThreadSafeRefCntPtr.h" #include "SourceKit/Support/Tracing.h" #include "SwiftInterfaceGenContext.h" +#include "swift/AST/DiagnosticConsumer.h" #include "swift/Basic/ThreadSafeRefCounted.h" #include "swift/IDE/CancellableResult.h" #include "swift/IDE/CompletionInstance.h" @@ -472,6 +473,23 @@ class SwiftLangSupport : public LangSupport { swift::ide::CancellableResult)> Callback); + /// The result returned from \c performWithParamsToCompletionLikeOperation. + struct CompletionLikeOperationParams { + swift::CompilerInvocation &Invocation; + llvm::MemoryBuffer *completionBuffer; + swift::DiagnosticConsumer *DiagC; + }; + + /// Execute \p PerformOperation sychronously with the parameters necessary to + /// invoke a completion-like operation on \c CompletionInstance. + void performWithParamsToCompletionLikeOperation( + llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, + ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::function_ref< + void(swift::ide::CancellableResult)> + PerformOperation); + //==========================================================================// // LangSupport Interface //==========================================================================// diff --git a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp index bc080157c2cb9..a756d96db25e1 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp @@ -30,100 +30,13 @@ static void translateTypeContextInfoOptions(OptionsDictionary &from, // TypeContextInfo doesn't receive any options at this point. } -/// The results returned from \c swiftTypeContextInfoImpl via \c Callback. -struct SwiftTypeContextInfoImplResult { - /// The actual results. If empty, no results were found. - ArrayRef Results; - /// Whether an AST was reused to produce the results. - bool DidReuseAST; -}; - -static void swiftTypeContextInfoImpl( - SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, - unsigned Offset, ArrayRef Args, - llvm::IntrusiveRefCntPtr FileSystem, - llvm::function_ref)> - Callback) { - assert(Callback && "Must provide callback to receive results"); - - using ResultType = CancellableResult; - - struct ConsumerToCallbackAdapter : public ide::TypeContextInfoConsumer { - bool ReusingASTContext; - llvm::function_ref Callback; - bool HandleResultsCalled = false; - - ConsumerToCallbackAdapter(bool ReusingASTContext, - llvm::function_ref Callback) - : ReusingASTContext(ReusingASTContext), Callback(Callback) {} - - void handleResults(ArrayRef Results) override { - HandleResultsCalled = true; - Callback(ResultType::success({Results, ReusingASTContext})); - } - }; - - Lang.performCompletionLikeOperation( - UnresolvedInputFile, Offset, Args, FileSystem, - [&](CancellableResult Result) { - switch (Result.getKind()) { - case CancellableResultKind::Success: { - ConsumerToCallbackAdapter Consumer(Result->DidReuseAST, Callback); - - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeTypeContextInfoCallbacksFactory(Consumer)); - - if (!Result->DidFindCodeCompletionToken) { - Callback( - ResultType::success({/*Results=*/{}, Result->DidReuseAST})); - } - - performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), - *callbacksFactory); - if (!Consumer.HandleResultsCalled) { - // If we didn't receive a handleResult call from the second pass, - // we didn't receive any results. To make sure Callback gets called - // exactly once, call it manually with no results here. - Callback( - ResultType::success({/*Results=*/{}, Result->DidReuseAST})); - } - break; - } - case CancellableResultKind::Failure: - Callback(ResultType::failure(Result.getError())); - break; - case CancellableResultKind::Cancelled: - Callback(ResultType::cancelled()); - break; - } - }); -} - -void SwiftLangSupport::getExpressionContextInfo( - llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, - OptionsDictionary *optionsDict, ArrayRef Args, - SourceKit::TypeContextInfoConsumer &SKConsumer, - Optional vfsOptions) { - std::string error; - - TypeContextInfo::Options options; - if (optionsDict) { - translateTypeContextInfoOptions(*optionsDict, options); - } - - // FIXME: the use of None as primary file is to match the fact we do not read - // the document contents using the editor documents infrastructure. - auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); - if (!fileSystem) - return SKConsumer.failed(error); +void deliverResults(SourceKit::TypeContextInfoConsumer &SKConsumer, + CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + SKConsumer.setReusingASTContext(Result->DidReuseAST); - class Consumer : public ide::TypeContextInfoConsumer { - SourceKit::TypeContextInfoConsumer &SKConsumer; - - /// Convert an IDE result to a SK result and send it to \c SKConsumer. - void handleSingleResult(const ide::TypeContextInfoItem &Item) { + for (auto &Item : Result->Results) { SmallString<512> SS; llvm::raw_svector_ostream OS(SS); @@ -201,36 +114,46 @@ void SwiftLangSupport::getExpressionContextInfo( SKConsumer.handleResult(Info); } + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } +} - public: - Consumer(SourceKit::TypeContextInfoConsumer &SKConsumer) - : SKConsumer(SKConsumer){}; +void SwiftLangSupport::getExpressionContextInfo( + llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, + OptionsDictionary *optionsDict, ArrayRef Args, + SourceKit::TypeContextInfoConsumer &SKConsumer, + Optional vfsOptions) { + std::string error; - void handleResults(ArrayRef Results) override { - for (auto &Item : Results) - handleSingleResult(Item); - } + TypeContextInfo::Options options; + if (optionsDict) { + translateTypeContextInfoOptions(*optionsDict, options); + } - void setReusingASTContext(bool flag) { - SKConsumer.setReusingASTContext(flag); - } - } Consumer(SKConsumer); - - swiftTypeContextInfoImpl( - *this, UnresolvedInputFile, Offset, Args, fileSystem, - [&](CancellableResult Result) { - switch (Result.getKind()) { - case CancellableResultKind::Success: { - Consumer.handleResults(Result->Results); - Consumer.setReusingASTContext(Result->DidReuseAST); - break; - } - case CancellableResultKind::Failure: - SKConsumer.failed(Result.getError()); - break; - case CancellableResultKind::Cancelled: - SKConsumer.cancelled(); - break; - } + // FIXME: the use of None as primary file is to match the fact we do not read + // the document contents using the editor documents infrastructure. + auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); + if (!fileSystem) { + return SKConsumer.failed(error); + } + + performWithParamsToCompletionLikeOperation( + UnresolvedInputFile, Offset, Args, fileSystem, + [&](CancellableResult ParamsResult) { + ParamsResult.mapAsync( + [&](auto &CIParams, auto DeliverTransformed) { + getCompletionInstance()->typeContextInfo( + CIParams.Invocation, Args, fileSystem, + CIParams.completionBuffer, Offset, CIParams.DiagC, + DeliverTransformed); + }, + [&](auto Result) { deliverResults(SKConsumer, Result); }); }); } From 367c9819eff846bb3c0ca03a25375256c59b878e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 12:44:18 +0200 Subject: [PATCH 04/12] [SourceKit] Move invocation of code completion second pass for ConformingMethodList from SoruceKit to CompletionInstance --- include/swift/IDE/CompletionInstance.h | 19 +- lib/IDE/CompletionInstance.cpp | 62 +++- .../SwiftLang/SwiftConformingMethodList.cpp | 334 +++++++----------- 3 files changed, 203 insertions(+), 212 deletions(-) diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index ba4097089cd23..788cc20094335 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -15,6 +15,7 @@ #include "swift/Frontend/Frontend.h" #include "swift/IDE/CancellableResult.h" +#include "swift/IDE/ConformingMethodList.h" #include "swift/IDE/TypeContextInfo.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" @@ -52,11 +53,19 @@ struct CompletionInstanceResult { /// The results returned from \c CompletionInstance::typeContextInfo. struct TypeContextInfoResult { /// The actual results. If empty, no results were found. - ArrayRef Results; + ArrayRef Results; /// Whether an AST was reused to produce the results. bool DidReuseAST; }; +/// The results returned from \c CompletionInstance::conformingMethodList. +struct ConformingMethodListResults { + /// The actual results. If \c nullptr, no results were found. + const ConformingMethodListResult *Result; + /// Whether an AST was reused for the completion. + bool DidReuseAST; +}; + /// Manages \c CompilerInstance for completion like operations. class CompletionInstance { struct Options { @@ -141,6 +150,14 @@ class CompletionInstance { DiagnosticConsumer *DiagC, llvm::function_ref)> Callback); + + void conformingMethodList( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *completionBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, ArrayRef ExpectedTypeNames, + llvm::function_ref)> + Callback); }; } // namespace ide diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index ce96cf5a07e97..78f94ab4b8945 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -701,9 +701,65 @@ void swift::ide::CompletionInstance::typeContextInfo( *Result.CI.getCodeCompletionFile(), *callbacksFactory); if (!Consumer.HandleResultsCalled) { // If we didn't receive a handleResult call from the second - // pass, we didn't receive any results. To make sure - // DeliverTransformed gets called exactly once, call it with - // no results here. + // pass, we didn't receive any results. To make sure Callback + // gets called exactly once, call it manually with no results + // here. + DeliverTransformed( + ResultType::success({/*Results=*/{}, Result.DidReuseAST})); + } + }, + Callback); + }); +} + +void swift::ide::CompletionInstance::conformingMethodList( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *completionBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, ArrayRef ExpectedTypeNames, + llvm::function_ref)> + Callback) { + using ResultType = CancellableResult; + + struct ConsumerToCallbackAdapter + : public swift::ide::ConformingMethodListConsumer { + bool ReusingASTContext; + llvm::function_ref Callback; + bool HandleResultsCalled = false; + + ConsumerToCallbackAdapter(bool ReusingASTContext, + llvm::function_ref Callback) + : ReusingASTContext(ReusingASTContext), Callback(Callback) {} + + void handleResult(const ide::ConformingMethodListResult &result) override { + HandleResultsCalled = true; + Callback(ResultType::success({&result, ReusingASTContext})); + } + }; + + performOperation( + Invocation, Args, FileSystem, completionBuffer, Offset, DiagC, + [&](CancellableResult CIResult) { + CIResult.mapAsync( + [&ExpectedTypeNames](auto &Result, auto DeliverTransformed) { + ConsumerToCallbackAdapter Consumer(Result.DidReuseAST, + DeliverTransformed); + std::unique_ptr callbacksFactory( + ide::makeConformingMethodListCallbacksFactory( + ExpectedTypeNames, Consumer)); + + if (!Result.DidFindCodeCompletionToken) { + DeliverTransformed( + ResultType::success({/*Results=*/{}, Result.DidReuseAST})); + } + + performCodeCompletionSecondPass( + *Result.CI.getCodeCompletionFile(), *callbacksFactory); + if (!Consumer.HandleResultsCalled) { + // If we didn't receive a handleResult call from the second + // pass, we didn't receive any results. To make sure Callback + // gets called exactly once, call it manually with no results + // here. DeliverTransformed( ResultType::success({/*Results=*/{}, Result.DidReuseAST})); } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp index 44c26459406f1..111b893039db4 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp @@ -32,80 +32,125 @@ translateConformingMethodListOptions(OptionsDictionary &from, // ConformingMethodList doesn't receive any options at this point. } -/// The results returned from \c swiftConformingMethodListImpl through the -/// callback. -struct ConformingMethodListImplResult { - /// The actual results. If \c nullptr, no results were found. - const ide::ConformingMethodListResult *Result; - /// Whether an AST was reused for the completion. - bool DidReuseAST; -}; - -static void swiftConformingMethodListImpl( - SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, - unsigned Offset, ArrayRef Args, - ArrayRef ExpectedTypeNames, - llvm::IntrusiveRefCntPtr FileSystem, - llvm::function_ref)> - Callback) { - assert(Callback && "Must provide callback to receive results"); - - using ResultType = CancellableResult; - - struct ConsumerToCallbackAdapter - : public swift::ide::ConformingMethodListConsumer { - bool ReusingASTContext; - llvm::function_ref Callback; - bool HandleResultWasCalled = false; - - ConsumerToCallbackAdapter(bool ReusingASTContext, - llvm::function_ref Callback) - : ReusingASTContext(ReusingASTContext), Callback(Callback) {} - - void handleResult(const ide::ConformingMethodListResult &result) override { - HandleResultWasCalled = true; - Callback(ResultType::success({&result, ReusingASTContext})); +void deliverResults(SourceKit::ConformingMethodListConsumer &SKConsumer, + CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + SKConsumer.setReusingASTContext(Result->DidReuseAST); + + if (!Result->Result) { + // If we have no results, don't call SKConsumer.handleResult which causes + // empty results to be delivered. + break; } - }; - Lang.performCompletionLikeOperation( - UnresolvedInputFile, Offset, Args, FileSystem, - [&](CancellableResult Result) { - switch (Result.getKind()) { - case CancellableResultKind::Success: { - ConsumerToCallbackAdapter Consumer(Result->DidReuseAST, Callback); + SmallString<512> SS; + llvm::raw_svector_ostream OS(SS); + + unsigned TypeNameBegin = SS.size(); + Result->Result->ExprType.print(OS); + unsigned TypeNameLength = SS.size() - TypeNameBegin; + + unsigned TypeUSRBegin = SS.size(); + SwiftLangSupport::printTypeUSR(Result->Result->ExprType, OS); + unsigned TypeUSRLength = SS.size() - TypeUSRBegin; + + struct MemberInfo { + size_t DeclNameBegin = 0; + size_t DeclNameLength = 0; + size_t TypeNameBegin = 0; + size_t TypeNameLength = 0; + size_t TypeUSRBegin = 0; + size_t TypeUSRLength = 0; + size_t DescriptionBegin = 0; + size_t DescriptionLength = 0; + size_t SourceTextBegin = 0; + size_t SourceTextLength = 0; + StringRef BriefComment; + + MemberInfo() {} + }; + SmallVector Members; + + for (auto member : Result->Result->Members) { + Members.emplace_back(); + auto &memberElem = Members.back(); + + auto funcTy = cast(member)->getMethodInterfaceType(); + funcTy = Result->Result->ExprType->getTypeOfMember( + Result->Result->DC->getParentModule(), member, funcTy); + auto resultTy = funcTy->castTo()->getResult(); + + // Name. + memberElem.DeclNameBegin = SS.size(); + member->getName().print(OS); + memberElem.DeclNameLength = SS.size() - memberElem.DeclNameBegin; + + // Type name. + memberElem.TypeNameBegin = SS.size(); + resultTy.print(OS); + memberElem.TypeNameLength = SS.size() - memberElem.TypeNameBegin; + + // Type USR. + memberElem.TypeUSRBegin = SS.size(); + SwiftLangSupport::printTypeUSR(resultTy, OS); + memberElem.TypeUSRLength = SS.size() - memberElem.TypeUSRBegin; + + // Description. + memberElem.DescriptionBegin = SS.size(); + SwiftLangSupport::printMemberDeclDescription( + member, Result->Result->ExprType, /*usePlaceholder=*/false, OS); + memberElem.DescriptionLength = SS.size() - memberElem.DescriptionBegin; + + // Sourcetext. + memberElem.SourceTextBegin = SS.size(); + SwiftLangSupport::printMemberDeclDescription( + member, Result->Result->ExprType, /*usePlaceholder=*/true, OS); + memberElem.SourceTextLength = SS.size() - memberElem.SourceTextBegin; + + // DocBrief. + auto MaybeClangNode = member->getClangNode(); + if (MaybeClangNode) { + if (auto *D = MaybeClangNode.getAsDecl()) { + const auto &ClangContext = D->getASTContext(); + if (const clang::RawComment *RC = + ClangContext.getRawCommentForAnyRedecl(D)) + memberElem.BriefComment = RC->getBriefText(ClangContext); + } + } else { + memberElem.BriefComment = member->getBriefComment(); + } + } - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames, - Consumer)); + SourceKit::ConformingMethodListResult SKResult; + SmallVector SKMembers; + + for (auto info : Members) { + StringRef Name(SS.begin() + info.DeclNameBegin, info.DeclNameLength); + StringRef TypeName(SS.begin() + info.TypeNameBegin, info.TypeNameLength); + StringRef TypeUSR(SS.begin() + info.TypeUSRBegin, info.TypeUSRLength); + StringRef Description(SS.begin() + info.DescriptionBegin, + info.DescriptionLength); + StringRef SourceText(SS.begin() + info.SourceTextBegin, + info.SourceTextLength); + SKMembers.push_back({Name, TypeName, TypeUSR, Description, SourceText, + info.BriefComment}); + } - if (!Result->DidFindCodeCompletionToken) { - Callback(ResultType::success( - {/*Results=*/nullptr, Result->DidReuseAST})); - return; - } + SKResult.TypeName = StringRef(SS.begin() + TypeNameBegin, TypeNameLength); + SKResult.TypeUSR = StringRef(SS.begin() + TypeUSRBegin, TypeUSRLength); + SKResult.Members = SKMembers; - performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), - *callbacksFactory); - if (!Consumer.HandleResultWasCalled) { - // If we didn't receive a handleResult call from the second pass, - // we didn't receive any results. To make sure Callback gets called - // exactly once, call it manually with no results here. - Callback(ResultType::success( - {/*Results=*/nullptr, Result->DidReuseAST})); - } - break; - } - case CancellableResultKind::Failure: - Callback(ResultType::failure(Result.getError())); - break; - case CancellableResultKind::Cancelled: - Callback(ResultType::cancelled()); - break; - } - }); + SKConsumer.handleResult(SKResult); + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } } void SwiftLangSupport::getConformingMethodList( @@ -119,151 +164,24 @@ void SwiftLangSupport::getConformingMethodList( // FIXME: the use of None as primary file is to match the fact we do not read // the document contents using the editor documents infrastructure. auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); - if (!fileSystem) + if (!fileSystem) { return SKConsumer.failed(error); + } ConformingMethodList::Options options; if (optionsDict) { translateConformingMethodListOptions(*optionsDict, options); } - class Consumer : public ide::ConformingMethodListConsumer { - SourceKit::ConformingMethodListConsumer &SKConsumer; - - public: - Consumer(SourceKit::ConformingMethodListConsumer &SKConsumer) - : SKConsumer(SKConsumer) {} - - /// Convert an IDE result to a SK result and send it to \c SKConsumer . - void handleResult(const ide::ConformingMethodListResult &Result) override { - SmallString<512> SS; - llvm::raw_svector_ostream OS(SS); - - unsigned TypeNameBegin = SS.size(); - Result.ExprType.print(OS); - unsigned TypeNameLength = SS.size() - TypeNameBegin; - - unsigned TypeUSRBegin = SS.size(); - SwiftLangSupport::printTypeUSR(Result.ExprType, OS); - unsigned TypeUSRLength = SS.size() - TypeUSRBegin; - - struct MemberInfo { - size_t DeclNameBegin = 0; - size_t DeclNameLength = 0; - size_t TypeNameBegin = 0; - size_t TypeNameLength = 0; - size_t TypeUSRBegin = 0; - size_t TypeUSRLength = 0; - size_t DescriptionBegin = 0; - size_t DescriptionLength = 0; - size_t SourceTextBegin = 0; - size_t SourceTextLength = 0; - StringRef BriefComment; - - MemberInfo() {} - }; - SmallVector Members; - - for (auto member : Result.Members) { - Members.emplace_back(); - auto &memberElem = Members.back(); - - auto funcTy = cast(member)->getMethodInterfaceType(); - funcTy = Result.ExprType->getTypeOfMember(Result.DC->getParentModule(), - member, funcTy); - auto resultTy = funcTy->castTo()->getResult(); - - // Name. - memberElem.DeclNameBegin = SS.size(); - member->getName().print(OS); - memberElem.DeclNameLength = SS.size() - memberElem.DeclNameBegin; - - // Type name. - memberElem.TypeNameBegin = SS.size(); - resultTy.print(OS); - memberElem.TypeNameLength = SS.size() - memberElem.TypeNameBegin; - - // Type USR. - memberElem.TypeUSRBegin = SS.size(); - SwiftLangSupport::printTypeUSR(resultTy, OS); - memberElem.TypeUSRLength = SS.size() - memberElem.TypeUSRBegin; - - // Description. - memberElem.DescriptionBegin = SS.size(); - SwiftLangSupport::printMemberDeclDescription( - member, Result.ExprType, /*usePlaceholder=*/false, OS); - memberElem.DescriptionLength = - SS.size() - memberElem.DescriptionBegin; - - // Sourcetext. - memberElem.SourceTextBegin = SS.size(); - SwiftLangSupport::printMemberDeclDescription( - member, Result.ExprType, /*usePlaceholder=*/true, OS); - memberElem.SourceTextLength = - SS.size() - memberElem.SourceTextBegin; - - // DocBrief. - auto MaybeClangNode = member->getClangNode(); - if (MaybeClangNode) { - if (auto *D = MaybeClangNode.getAsDecl()) { - const auto &ClangContext = D->getASTContext(); - if (const clang::RawComment *RC = - ClangContext.getRawCommentForAnyRedecl(D)) - memberElem.BriefComment = RC->getBriefText(ClangContext); - } - } else { - memberElem.BriefComment = member->getBriefComment(); - } - } - - SourceKit::ConformingMethodListResult SKResult; - SmallVector - SKMembers; - - for (auto info : Members) { - StringRef Name(SS.begin() + info.DeclNameBegin, info.DeclNameLength); - StringRef TypeName(SS.begin() + info.TypeNameBegin, - info.TypeNameLength); - StringRef TypeUSR(SS.begin() + info.TypeUSRBegin, info.TypeUSRLength); - StringRef Description(SS.begin() + info.DescriptionBegin, - info.DescriptionLength); - StringRef SourceText(SS.begin() + info.SourceTextBegin, - info.SourceTextLength); - SKMembers.push_back({Name, TypeName, TypeUSR, Description, SourceText, - info.BriefComment}); - } - - SKResult.TypeName = StringRef(SS.begin() + TypeNameBegin, TypeNameLength); - SKResult.TypeUSR = StringRef(SS.begin() + TypeUSRBegin, TypeUSRLength); - SKResult.Members = SKMembers; - - SKConsumer.handleResult(SKResult); - } - - void setReusingASTContext(bool flag) { - SKConsumer.setReusingASTContext(flag); - } - } Consumer(SKConsumer); - - swiftConformingMethodListImpl( - *this, UnresolvedInputFile, Offset, Args, ExpectedTypeNames, fileSystem, - [&](CancellableResult Result) { - switch (Result.getKind()) { - case CancellableResultKind::Success: { - if (Result->Result) { - Consumer.handleResult(*Result->Result); - } - // If we didn't receive any result, don't call handleResult on - // Consumer to deliver empty results. - Consumer.setReusingASTContext(Result->DidReuseAST); - break; - } - case CancellableResultKind::Failure: - SKConsumer.failed(Result.getError()); - break; - case CancellableResultKind::Cancelled: - SKConsumer.cancelled(); - break; - } + performWithParamsToCompletionLikeOperation( + UnresolvedInputFile, Offset, Args, fileSystem, + [&](CancellableResult ParmsResult) { + ParmsResult.mapAsync( + [&](auto &Params, auto DeliverTransformed) { + getCompletionInstance()->conformingMethodList( + Params.Invocation, Args, fileSystem, Params.completionBuffer, + Offset, Params.DiagC, ExpectedTypeNames, DeliverTransformed); + }, + [&](auto Result) { deliverResults(SKConsumer, Result); }); }); } From 163ccf91840afdcd517fe9ac3cf89b1a2133daa7 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 12:50:54 +0200 Subject: [PATCH 05/12] [SourceKit] Move invocation of code completion second pass for code completion from SoruceKit to CompletionInstance --- include/swift/IDE/CodeCompletion.h | 7 + include/swift/IDE/CompletionInstance.h | 14 + lib/IDE/CompletionInstance.cpp | 76 +++++ .../lib/SwiftLang/CodeCompletionOrganizer.h | 9 +- .../lib/SwiftLang/SwiftCompletion.cpp | 306 ++++++------------ 5 files changed, 193 insertions(+), 219 deletions(-) diff --git a/include/swift/IDE/CodeCompletion.h b/include/swift/IDE/CodeCompletion.h index c91cf48f7c36b..0f0035c1e045d 100644 --- a/include/swift/IDE/CodeCompletion.h +++ b/include/swift/IDE/CodeCompletion.h @@ -17,6 +17,7 @@ #include "swift/Basic/Debug.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/OptionSet.h" +#include "swift/Frontend/Frontend.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -1062,6 +1063,12 @@ class CodeCompletionContext { } }; +struct SwiftCompletionInfo { + swift::ASTContext *swiftASTContext = nullptr; + const swift::CompilerInvocation *invocation = nullptr; + CodeCompletionContext *completionContext = nullptr; +}; + /// An abstract base class for consumers of code completion results. /// \see \c SimpleCachingCodeCompletionConsumer. class CodeCompletionConsumer { diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index 788cc20094335..28eef54639a18 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -15,6 +15,7 @@ #include "swift/Frontend/Frontend.h" #include "swift/IDE/CancellableResult.h" +#include "swift/IDE/CodeCompletion.h" #include "swift/IDE/ConformingMethodList.h" #include "swift/IDE/TypeContextInfo.h" #include "llvm/ADT/Hashing.h" @@ -50,6 +51,12 @@ struct CompletionInstanceResult { bool DidFindCodeCompletionToken; }; +/// The results returned from \c CompletionInstance::codeComplete. +struct CodeCompleteResult { + MutableArrayRef Results; + SwiftCompletionInfo &Info; +}; + /// The results returned from \c CompletionInstance::typeContextInfo. struct TypeContextInfoResult { /// The actual results. If empty, no results were found. @@ -143,6 +150,13 @@ class CompletionInstance { llvm::function_ref)> Callback); + void codeComplete( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *completionBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, ide::CodeCompletionContext &&CompletionContext, + llvm::function_ref)> Callback); + void typeContextInfo( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index 78f94ab4b8945..703e35b7ffb8f 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -25,6 +25,7 @@ #include "swift/ClangImporter/ClangModule.h" #include "swift/Driver/FrontendUtil.h" #include "swift/Frontend/Frontend.h" +#include "swift/IDE/CodeCompletion.h" #include "swift/Parse/Lexer.h" #include "swift/Parse/PersistentParserState.h" #include "swift/Serialization/SerializedModuleLoader.h" @@ -656,6 +657,81 @@ void swift::ide::CompletionInstance::performOperation( Offset, DiagC, Callback); } +void swift::ide::CompletionInstance::codeComplete( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *completionBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, ide::CodeCompletionContext &&CompletionContext, + llvm::function_ref)> Callback) { + using ResultType = CancellableResult; + + struct ConsumerToCallbackAdapter + : public SimpleCachingCodeCompletionConsumer { + SwiftCompletionInfo SwiftContext; + llvm::function_ref Callback; + bool HandleResultsCalled = false; + + ConsumerToCallbackAdapter(llvm::function_ref Callback) + : Callback(Callback) {} + + void setContext(swift::ASTContext *context, + const swift::CompilerInvocation *invocation, + swift::ide::CodeCompletionContext *completionContext) { + SwiftContext.swiftASTContext = context; + SwiftContext.invocation = invocation; + SwiftContext.completionContext = completionContext; + } + void clearContext() { SwiftContext = SwiftCompletionInfo(); } + + void handleResults(CodeCompletionContext &context) override { + HandleResultsCalled = true; + MutableArrayRef Results = context.takeResults(); + assert(SwiftContext.swiftASTContext); + Callback(ResultType::success({Results, SwiftContext})); + } + }; + + performOperation( + Invocation, Args, FileSystem, completionBuffer, Offset, DiagC, + [&](CancellableResult CIResult) { + CIResult.mapAsync( + [&CompletionContext](auto &Result, auto DeliverTransformed) { + CompletionContext.ReusingASTContext = Result.DidReuseAST; + CompilerInstance &CI = Result.CI; + ConsumerToCallbackAdapter Consumer(DeliverTransformed); + + std::unique_ptr callbacksFactory( + ide::makeCodeCompletionCallbacksFactory(CompletionContext, + Consumer)); + + if (!Result.DidFindCodeCompletionToken) { + SwiftCompletionInfo Info{&CI.getASTContext(), + &CI.getInvocation(), + &CompletionContext}; + DeliverTransformed(ResultType::success({/*Results=*/{}, Info})); + return; + } + + Consumer.setContext(&CI.getASTContext(), &CI.getInvocation(), + &CompletionContext); + performCodeCompletionSecondPass(*CI.getCodeCompletionFile(), + *callbacksFactory); + Consumer.clearContext(); + if (!Consumer.HandleResultsCalled) { + // If we didn't receive a handleResult call from the second + // pass, we didn't receive any results. To make sure Callback + // gets called exactly once, call it manually with no results + // here. + SwiftCompletionInfo Info{&CI.getASTContext(), + &CI.getInvocation(), + &CompletionContext}; + DeliverTransformed(ResultType::success({/*Results=*/{}, Info})); + } + }, + Callback); + }); +} + void swift::ide::CompletionInstance::typeContextInfo( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, diff --git a/tools/SourceKit/lib/SwiftLang/CodeCompletionOrganizer.h b/tools/SourceKit/lib/SwiftLang/CodeCompletionOrganizer.h index 65c6e4c729ca8..3bbf997005d04 100644 --- a/tools/SourceKit/lib/SwiftLang/CodeCompletionOrganizer.h +++ b/tools/SourceKit/lib/SwiftLang/CodeCompletionOrganizer.h @@ -15,6 +15,7 @@ #include "CodeCompletion.h" #include "SourceKit/Core/LangSupport.h" +#include "swift/IDE/CodeCompletion.h" #include "llvm/ADT/StringMap.h" namespace swift { @@ -53,17 +54,11 @@ struct Options { unsigned popularityBonus = 2; }; -struct SwiftCompletionInfo { - swift::ASTContext *swiftASTContext = nullptr; - const swift::CompilerInvocation *invocation = nullptr; - swift::ide::CodeCompletionContext *completionContext = nullptr; -}; - typedef llvm::StringMap NameToPopularityMap; std::vector extendCompletions(ArrayRef swiftResults, CompletionSink &sink, - SwiftCompletionInfo &info, + swift::ide::SwiftCompletionInfo &info, const NameToPopularityMap *nameToPopularity, const Options &options, Completion *prefix = nullptr, bool clearFlair = false); diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp index 5027831632802..87a1782ddc3b1 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp @@ -31,11 +31,11 @@ using namespace SourceKit; using namespace swift; using namespace ide; -using CodeCompletion::SwiftCompletionInfo; -using CodeCompletion::Completion; using CodeCompletion::CodeCompletionView; using CodeCompletion::CodeCompletionViewRef; +using CodeCompletion::Completion; using CodeCompletion::NameToPopularityMap; +using ide::SwiftCompletionInfo; static_assert(swift::ide::CodeCompletionResult::MaxNumBytesToErase == 127, "custom array implementation for code completion results " @@ -78,31 +78,6 @@ struct SwiftToSourceKitCompletionAdapter { raw_ostream &OS); }; -struct SwiftCodeCompletionConsumer - : public ide::SimpleCachingCodeCompletionConsumer { - using HandlerFunc = std::function, SwiftCompletionInfo &)>; - HandlerFunc handleResultsImpl; - SwiftCompletionInfo swiftContext; - - SwiftCodeCompletionConsumer(HandlerFunc handleResultsImpl) - : handleResultsImpl(handleResultsImpl) {} - - void setContext(swift::ASTContext *context, - const swift::CompilerInvocation *invocation, - swift::ide::CodeCompletionContext *completionContext) { - swiftContext.swiftASTContext = context; - swiftContext.invocation = invocation; - swiftContext.completionContext = completionContext; - } - void clearContext() { swiftContext = SwiftCompletionInfo(); } - - void handleResults(CodeCompletionContext &context) override { - MutableArrayRef Results = context.takeResults(); - assert(swiftContext.swiftASTContext); - handleResultsImpl(Results, swiftContext); - } -}; } // anonymous namespace /// Returns completion context kind \c UIdent to report to the client. @@ -135,87 +110,28 @@ static void swiftCodeCompleteImpl( unsigned Offset, ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, const CodeCompletion::Options &opts, - llvm::function_ref)> - Callback) { + llvm::function_ref)> Callback) { assert(Callback && "Must provide callback to receive results"); - using ResultType = CancellableResult; - - /// Receive code completion results via \c handleResultsAndModules and calls - /// \c Callback with the corresponding values. - struct ConsumerToCallbackAdapter : public swift::ide::CodeCompletionConsumer { - CompilerInstance &CI; - ide::CodeCompletionContext &CompletionContext; - llvm::function_ref Callback; - bool HandleResultWasCalled = false; - - ConsumerToCallbackAdapter(CompilerInstance &CI, - ide::CodeCompletionContext &CompletionContext, - llvm::function_ref Callback) - : CI(CI), CompletionContext(CompletionContext), Callback(Callback) {} - - void - handleResultsAndModules(CodeCompletionContext &context, - ArrayRef requestedModules, - DeclContext *DC) override { - HandleResultWasCalled = true; - assert(&context == &CompletionContext); - Callback(ResultType::success({/*HasResults=*/true, &CI.getASTContext(), - &CI.getInvocation(), &CompletionContext, - requestedModules, DC})); - } - }; + auto swiftCache = Lang.getCodeCompletionCache(); // Pin the cache. + ide::CodeCompletionContext CompletionContext(swiftCache->getCache()); + CompletionContext.setAnnotateResult(opts.annotatedDescription); + CompletionContext.setIncludeObjectLiterals(opts.includeObjectLiterals); + CompletionContext.setAddInitsToTopLevel(opts.addInitsToTopLevel); + CompletionContext.setCallPatternHeuristics(opts.callPatternHeuristics); - Lang.performCompletionLikeOperation( + Lang.performWithParamsToCompletionLikeOperation( UnresolvedInputFile, Offset, Args, FileSystem, - [&](CancellableResult Result) { - switch (Result.getKind()) { - case CancellableResultKind::Success: { - // Create a factory for code completion callbacks that will feed the - // Consumer. - auto swiftCache = Lang.getCodeCompletionCache(); // Pin the cache. - ide::CodeCompletionContext CompletionContext(swiftCache->getCache()); - CompletionContext.ReusingASTContext = Result->DidReuseAST; - CompletionContext.setAnnotateResult(opts.annotatedDescription); - CompletionContext.setIncludeObjectLiterals( - opts.includeObjectLiterals); - CompletionContext.setAddInitsToTopLevel(opts.addInitsToTopLevel); - CompletionContext.setCallPatternHeuristics( - opts.callPatternHeuristics); - - CompilerInstance &CI = Result->CI; - ConsumerToCallbackAdapter Consumer(CI, CompletionContext, Callback); - - std::unique_ptr callbacksFactory( - ide::makeCodeCompletionCallbacksFactory(CompletionContext, - Consumer)); - - if (!Result->DidFindCodeCompletionToken) { - Callback(ResultType::success( - {/*HasResults=*/false, &CI.getASTContext(), &CI.getInvocation(), - &CompletionContext, /*RequestedModules=*/{}, /*DC=*/nullptr})); - return; - } - - performCodeCompletionSecondPass(*CI.getCodeCompletionFile(), - *callbacksFactory); - if (!Consumer.HandleResultWasCalled) { - // If we didn't receive a handleResult call from the second pass, - // we didn't receive any results. To make sure Callback gets called - // exactly once, call it manually with no results here. - Callback(ResultType::success( - {/*HasResults=*/false, &CI.getASTContext(), &CI.getInvocation(), - &CompletionContext, /*RequestedModules=*/{}, /*DC=*/nullptr})); - } - break; - } - case CancellableResultKind::Failure: - Callback(ResultType::failure(Result.getError())); - break; - case CancellableResultKind::Cancelled: - Callback(ResultType::cancelled()); - break; - } + [&](CancellableResult + ParamsResult) { + ParamsResult.mapAsync( + [&](auto &CIParams, auto DeliverTransformed) { + Lang.getCompletionInstance()->codeComplete( + CIParams.Invocation, Args, FileSystem, + CIParams.completionBuffer, Offset, CIParams.DiagC, + std::move(CompletionContext), DeliverTransformed); + }, + Callback); }); } @@ -225,43 +141,23 @@ static void translateCodeCompletionOptions(OptionsDictionary &from, unsigned &resultOffset, unsigned &maxResults); -void SwiftLangSupport::codeComplete( - llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, - OptionsDictionary *options, - SourceKit::CodeCompletionConsumer &SKConsumer, ArrayRef Args, - Optional vfsOptions) { - - CodeCompletion::Options CCOpts; - if (options) { - StringRef filterText; - unsigned resultOffset = 0; - unsigned maxResults = 0; - translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset, - maxResults); - } - - std::string error; - // FIXME: the use of None as primary file is to match the fact we do not read - // the document contents using the editor documents infrastructure. - auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); - if (!fileSystem) - return SKConsumer.failed(error); - - SwiftCodeCompletionConsumer SwiftConsumer([&]( - MutableArrayRef Results, - SwiftCompletionInfo &info) { - +void deliverCodeCompleteResults(SourceKit::CodeCompletionConsumer &SKConsumer, + const CodeCompletion::Options &CCOpts, + CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { auto kind = getUIDForCodeCompletionKindToReport( - info.completionContext->CodeCompletionKind); + Result->Info.completionContext->CodeCompletionKind); if (kind.isValid()) SKConsumer.setCompletionKind(kind); - bool hasRequiredType = info.completionContext->typeContextKind == TypeContextKind::Required; + bool hasRequiredType = Result->Info.completionContext->typeContextKind == + TypeContextKind::Required; if (CCOpts.sortByName) - CodeCompletionContext::sortCompletionResults(Results); + CodeCompletionContext::sortCompletionResults(Result->Results); // FIXME: this adhoc filtering should be configurable like it is in the // codeCompleteOpen path. - for (auto *Result : Results) { + for (auto *Result : Result->Results) { if (Result->getKind() == CodeCompletionResult::Literal) { switch (Result->getLiteralKind()) { case CodeCompletionLiteralKind::NilLiteral: @@ -283,35 +179,47 @@ void SwiftLangSupport::codeComplete( break; } - SKConsumer.setReusingASTContext(info.completionContext->ReusingASTContext); - SKConsumer.setAnnotatedTypename(info.completionContext->getAnnotateResult()); - }); + SKConsumer.setReusingASTContext( + Result->Info.completionContext->ReusingASTContext); + SKConsumer.setAnnotatedTypename( + Result->Info.completionContext->getAnnotateResult()); + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } +} + +void SwiftLangSupport::codeComplete( + llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, + OptionsDictionary *options, SourceKit::CodeCompletionConsumer &SKConsumer, + ArrayRef Args, Optional vfsOptions) { + + CodeCompletion::Options CCOpts; + if (options) { + StringRef filterText; + unsigned resultOffset = 0; + unsigned maxResults = 0; + translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset, + maxResults); + } + + std::string error; + // FIXME: the use of None as primary file is to match the fact we do not read + // the document contents using the editor documents infrastructure. + auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error); + if (!fileSystem) { + return SKConsumer.failed(error); + } swiftCodeCompleteImpl( *this, UnresolvedInputFile, Offset, Args, fileSystem, CCOpts, - [&](CancellableResult Result) { - switch (Result.getKind()) { - case CancellableResultKind::Success: { - if (Result->HasResults) { - SwiftConsumer.setContext(Result->Context, Result->Invocation, - Result->CompletionContext); - SwiftConsumer.handleResultsAndModules(*Result->CompletionContext, - Result->RequestedModules, - Result->DC); - SwiftConsumer.clearContext(); - // SwiftConsumer will deliver the results to SKConsumer. - } - // If we don't have any results, don't call anything on SwiftConsumer. - // This will cause us to deliver empty results. - break; - } - case CancellableResultKind::Failure: - SKConsumer.failed(Result.getError()); - break; - case CancellableResultKind::Cancelled: - SKConsumer.cancelled(); - break; - } + [&](CancellableResult Result) { + deliverCodeCompleteResults(SKConsumer, CCOpts, Result); }); } @@ -1094,21 +1002,6 @@ static void transformAndForwardResults( bool hasDot = false; bool hasQDot = false; bool hasInit = false; - SwiftCodeCompletionConsumer swiftConsumer([&]( - MutableArrayRef results, - SwiftCompletionInfo &info) { - auto *context = info.completionContext; - if (!context || context->CodeCompletionKind != CompletionKind::PostfixExpr) - return; - auto topResults = filterInnerResults(results, options.addInnerResults, - options.addInnerOperators, hasDot, - hasQDot, hasInit, rules); - // FIXME: Clearing the flair (and semantic context) is a hack so that - // they won't overwhelm other results that also match the filter text. - innerResults = extendCompletions( - topResults, innerSink, info, nameToPopularity, options, exactMatch, - /*clearFlair=*/true); - }); auto *inputBuf = session->getBuffer(); std::string str = inputBuf->getBuffer().slice(0, offset).str(); @@ -1129,30 +1022,35 @@ static void transformAndForwardResults( swiftCodeCompleteImpl( lang, buffer.get(), str.size(), cargs, session->getFileSystem(), - options, [&](CancellableResult Result) { + options, [&](CancellableResult Result) { CallbackCalled = true; switch (Result.getKind()) { case CancellableResultKind::Success: { - if (Result->HasResults) { - swiftConsumer.setContext(Result->Context, Result->Invocation, - Result->CompletionContext); - swiftConsumer.handleResultsAndModules(*Result->CompletionContext, - Result->RequestedModules, - Result->DC); - swiftConsumer.clearContext(); + auto *context = Result->Info.completionContext; + if (!context || + context->CodeCompletionKind != CompletionKind::PostfixExpr) { + return; } - // If we didn't receive any results, don't call any method on - // swiftConsumer. This will cause us to deliver empty results. + auto topResults = filterInnerResults( + Result->Results, options.addInnerResults, + options.addInnerOperators, hasDot, hasQDot, hasInit, rules); + // FIXME: Clearing the flair (and semantic context) is a hack so + // that they won't overwhelm other results that also match the + // filter text. + innerResults = + extendCompletions(topResults, innerSink, Result->Info, + nameToPopularity, options, exactMatch, + /*clearFlair=*/true); break; } case CancellableResultKind::Failure: CodeCompleteDidFail = true; consumer.failed(Result.getError()); - return; + break; case CancellableResultKind::Cancelled: CodeCompleteDidFail = true; consumer.cancelled(); - return; + break; } }); assert(CallbackCalled && @@ -1236,38 +1134,22 @@ void SwiftLangSupport::codeCompleteOpen( TypeContextKind typeContextKind = TypeContextKind::None; bool mayUseImplicitMemberExpr = false; - SwiftCodeCompletionConsumer swiftConsumer( - [&](MutableArrayRef results, - SwiftCompletionInfo &info) { - auto &completionCtx = *info.completionContext; - completionKind = completionCtx.CodeCompletionKind; - typeContextKind = completionCtx.typeContextKind; - mayUseImplicitMemberExpr = completionCtx.MayUseImplicitMemberExpr; - consumer.setReusingASTContext(completionCtx.ReusingASTContext); - consumer.setAnnotatedTypename(completionCtx.getAnnotateResult()); - completions = - extendCompletions(results, sink, info, nameToPopularity, CCOpts); - }); - bool CodeCompleteDidFail = false; bool CallbackCalled = false; - // Invoke completion. swiftCodeCompleteImpl( *this, inputBuf, offset, args, fileSystem, CCOpts, - [&](CancellableResult Result) { + [&](CancellableResult Result) { CallbackCalled = true; switch (Result.getKind()) { case CancellableResultKind::Success: { - if (Result->HasResults) { - swiftConsumer.setContext(Result->Context, Result->Invocation, - Result->CompletionContext); - swiftConsumer.handleResultsAndModules(*Result->CompletionContext, - Result->RequestedModules, - Result->DC); - swiftConsumer.clearContext(); - } - // If we didn't receive any results, don't call any method on - // swiftConsumer. This will cause us to deliver empty results. + auto &completionCtx = *Result->Info.completionContext; + completionKind = completionCtx.CodeCompletionKind; + typeContextKind = completionCtx.typeContextKind; + mayUseImplicitMemberExpr = completionCtx.MayUseImplicitMemberExpr; + consumer.setReusingASTContext(completionCtx.ReusingASTContext); + consumer.setAnnotatedTypename(completionCtx.getAnnotateResult()); + completions = extendCompletions(Result->Results, sink, Result->Info, + nameToPopularity, CCOpts); break; } case CancellableResultKind::Failure: From 70e3c99edd3a0c46d13fed32d81644989fe77a2d Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 12:52:03 +0200 Subject: [PATCH 06/12] [SourceKit] Remove performCompletionLikeOperation All users of `performCodeCompletionLikeOperation` have been migrated to dedicated methods on `CompletionInstance`. --- .../lib/SwiftLang/SwiftLangSupport.cpp | 20 ------------------- .../lib/SwiftLang/SwiftLangSupport.h | 11 ---------- 2 files changed, 31 deletions(-) diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index 2593e12a3b184..f3205918fae27 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -1069,26 +1069,6 @@ void SwiftLangSupport::performWithParamsToCompletionLikeOperation( CancellableResult::success(Params)); } -void SwiftLangSupport::performCompletionLikeOperation( - llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, - ArrayRef Args, - llvm::IntrusiveRefCntPtr FileSystem, - llvm::function_ref)> - Callback) { - performWithParamsToCompletionLikeOperation( - UnresolvedInputFile, Offset, Args, FileSystem, - [&](CancellableResult ParamsResult) { - ParamsResult.mapAsync( - [&](auto &CIParams, auto DeliverTransformed) { - getCompletionInstance()->performOperation( - CIParams.Invocation, Args, FileSystem, - CIParams.completionBuffer, Offset, CIParams.DiagC, - DeliverTransformed); - }, - Callback); - }); -} - CloseClangModuleFiles::~CloseClangModuleFiles() { clang::Preprocessor &PP = loader.getClangPreprocessor(); clang::ModuleMap &ModMap = PP.getHeaderSearchInfo().getModuleMap(); diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index dcaacd4ef2c33..8034438a8987b 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -462,17 +462,6 @@ class SwiftLangSupport : public LangSupport { /// returns the original path; static std::string resolvePathSymlinks(StringRef FilePath); - /// Perform a completion like operation. It initializes a \c CompilerInstance, - /// the calls \p Callback with it. \p Callback must perform the second pass - /// using that instance. - void performCompletionLikeOperation( - llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, - ArrayRef Args, - llvm::IntrusiveRefCntPtr FileSystem, - llvm::function_ref)> - Callback); - /// The result returned from \c performWithParamsToCompletionLikeOperation. struct CompletionLikeOperationParams { swift::CompilerInvocation &Invocation; From 4ee9b0dec6547aba8a8c7e7ece922dac41bd52bd Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 13:02:05 +0200 Subject: [PATCH 07/12] [swift-ide-test] Use dedicated method for typeContextInfo on CompletionInstance instead of generic performOperation We are migrating all users of `performOperation` to dedicated methods on `CodeCompletionInstance`. Do the same in `swift-ide-test`. --- include/swift/IDE/TypeContextInfo.h | 10 -- lib/IDE/TypeContextInfo.cpp | 34 ---- tools/swift-ide-test/swift-ide-test.cpp | 211 +++++++++++++++++------- 3 files changed, 153 insertions(+), 102 deletions(-) diff --git a/include/swift/IDE/TypeContextInfo.h b/include/swift/IDE/TypeContextInfo.h index 2bb8c95053710..32dd2068c57ed 100644 --- a/include/swift/IDE/TypeContextInfo.h +++ b/include/swift/IDE/TypeContextInfo.h @@ -40,16 +40,6 @@ class TypeContextInfoConsumer { virtual void handleResults(ArrayRef) = 0; }; -/// Printing consumer -class PrintingTypeContextInfoConsumer : public TypeContextInfoConsumer { - llvm::raw_ostream &OS; - -public: - PrintingTypeContextInfoConsumer(llvm::raw_ostream &OS) : OS(OS) {} - - void handleResults(ArrayRef) override; -}; - /// Create a factory for code completion callbacks. CodeCompletionCallbacksFactory * makeTypeContextInfoCallbacksFactory(TypeContextInfoConsumer &Consumer); diff --git a/lib/IDE/TypeContextInfo.cpp b/lib/IDE/TypeContextInfo.cpp index 23c3476c8d94b..756dd5dafbf83 100644 --- a/lib/IDE/TypeContextInfo.cpp +++ b/lib/IDE/TypeContextInfo.cpp @@ -173,40 +173,6 @@ void ContextInfoCallbacks::getImplicitMembers( /*includeProtocolExtensionMembers*/true); } -void PrintingTypeContextInfoConsumer::handleResults( - ArrayRef results) { - OS << "-----BEGIN TYPE CONTEXT INFO-----\n"; - for (auto resultItem : results) { - OS << "- TypeName: "; - resultItem.ExpectedTy.print(OS); - OS << "\n"; - - OS << " TypeUSR: "; - printTypeUSR(resultItem.ExpectedTy, OS); - OS << "\n"; - - OS << " ImplicitMembers:"; - if (resultItem.ImplicitMembers.empty()) - OS << " []"; - OS << "\n"; - for (auto VD : resultItem.ImplicitMembers) { - OS << " - "; - - OS << "Name: "; - VD->getName().print(OS); - OS << "\n"; - - StringRef BriefDoc = VD->getBriefComment(); - if (!BriefDoc.empty()) { - OS << " DocBrief: \""; - OS << VD->getBriefComment(); - OS << "\"\n"; - } - } - } - OS << "-----END TYPE CONTEXT INFO-----\n"; -} - CodeCompletionCallbacksFactory *swift::ide::makeTypeContextInfoCallbacksFactory( TypeContextInfoConsumer &Consumer) { diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 21483f8fb44f9..eec12ecb1780a 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -831,13 +831,27 @@ static bool setBufferForFile(StringRef SourceFilename, return false; } -static bool doCodeCompletionImpl( - CodeCompletionCallbacksFactory *callbacksFactory, - const CompilerInvocation &InitInvok, - StringRef SourceFilename, - StringRef SecondSourceFileName, - StringRef CodeCompletionToken, - bool CodeCompletionDiagnostics) { +/// Result returned from \c performWithCompletionLikeOperationParams. +struct CompletionLikeOperationParams { + swift::CompilerInvocation &Invocation; + llvm::ArrayRef Args; + llvm::IntrusiveRefCntPtr FileSystem; + llvm::MemoryBuffer *CompletionBuffer; + unsigned int Offset; + swift::DiagnosticConsumer *DiagC; +}; + +/// Run \p PerformOperation with the parameters that are needed to perform a +/// completion like operation on a \c CompletionInstance. This function will +/// return the same value as \p PerformOperation. +/// In case there was an error setting up the parameters for the operation, +/// this method returns \c true and does not call \p PerformOperation. +static bool performWithCompletionLikeOperationParams( + const CompilerInvocation &InitInvok, StringRef SourceFilename, + StringRef SecondSourceFileName, StringRef CodeCompletionToken, + bool CodeCompletionDiagnostics, + llvm::function_ref + PerformOperation) { std::unique_ptr FileBuf; if (setBufferForFile(SourceFilename, FileBuf)) return true; @@ -865,49 +879,128 @@ static bool doCodeCompletionImpl( } PrintingDiagnosticConsumer PrintDiags; - CompletionInstance CompletionInst; - std::string CompletionError; - bool CallbackCalled = false; - CompletionInst.performOperation( - Invocation, /*Args=*/{}, llvm::vfs::getRealFileSystem(), CleanFile.get(), - Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr, - [&](CancellableResult Result) { - CallbackCalled = true; - switch (Result.getKind()) { - case CancellableResultKind::Success: { - assert(!Result->DidReuseAST && - "reusing AST context without enabling it"); - - if (!Result->DidFindCodeCompletionToken) { - // Return empty results by not performing the second pass and never - // calling the consumer of the callback factory. - return; - } - performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), - *callbacksFactory); - break; - } - case CancellableResultKind::Failure: - CompletionError = "error: " + Result.getError(); - break; - case CancellableResultKind::Cancelled: - CompletionError = "request cancelled"; - break; + CompletionLikeOperationParams Params{Invocation, + /*Args=*/{}, + llvm::vfs::getRealFileSystem(), + CleanFile.get(), + Offset, + CodeCompletionDiagnostics ? &PrintDiags + : nullptr}; + + return PerformOperation(Params); +} + +static bool +doCodeCompletionImpl(CodeCompletionCallbacksFactory *callbacksFactory, + const CompilerInvocation &InitInvok, + StringRef SourceFilename, StringRef SecondSourceFileName, + StringRef CodeCompletionToken, + bool CodeCompletionDiagnostics) { + return performWithCompletionLikeOperationParams( + InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, + CodeCompletionDiagnostics, + [&](CompletionLikeOperationParams Params) -> bool { + PrintingDiagnosticConsumer PrintDiags; + CompletionInstance CompletionInst; + std::string CompletionError; + bool CallbackCalled = false; + CompletionInst.performOperation( + Params.Invocation, Params.Args, Params.FileSystem, + Params.CompletionBuffer, Params.Offset, Params.DiagC, + [&](CancellableResult Result) { + CallbackCalled = true; + switch (Result.getKind()) { + case CancellableResultKind::Success: { + assert(!Result->DidReuseAST && + "reusing AST context without enabling it"); + + if (!Result->DidFindCodeCompletionToken) { + // Return empty results by not performing the second pass and + // never calling the consumer of the callback factory. + return; + } + + performCodeCompletionSecondPass( + *Result->CI.getCodeCompletionFile(), *callbacksFactory); + break; + } + case CancellableResultKind::Failure: + CompletionError = "error: " + Result.getError(); + break; + case CancellableResultKind::Cancelled: + CompletionError = "request cancelled"; + break; + } + }); + assert(CallbackCalled && + "Expected callback to be called synchronously"); + if (CompletionError == "error: did not find code completion token") { + // Do not consider failure to find the code completion token as a + // critical failure that returns a non-zero exit code. Instead, allow + // the caller to match the error message. + llvm::outs() << CompletionError << '\n'; + } else if (!CompletionError.empty()) { + llvm::errs() << CompletionError << '\n'; + return true; } + return false; }); - assert(CallbackCalled && - "We should always receive a callback call for code completion"); - if (CompletionError == "error: did not find code completion token") { - // Do not consider failure to find the code completion token as a critical - // failure that returns a non-zero exit code. Instead, allow the caller to - // match the error message. - llvm::outs() << CompletionError << '\n'; - } else if (!CompletionError.empty()) { - llvm::errs() << CompletionError << '\n'; - return true; +} + +template +static int printResult(CancellableResult Result, + llvm::function_ref PrintSuccess) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + return PrintSuccess(Result.getResult()); + } + case CancellableResultKind::Failure: + llvm::errs() << "error: " << Result.getError() << '\n'; + return 1; + case CancellableResultKind::Cancelled: + llvm::errs() << "request cancelled\n"; + return 1; + break; } - return false; +} + +static int printTypeContextInfo( + CancellableResult CancellableResult) { + return printResult( + CancellableResult, [](TypeContextInfoResult &Result) { + llvm::outs() << "-----BEGIN TYPE CONTEXT INFO-----\n"; + for (auto resultItem : Result.Results) { + llvm::outs() << "- TypeName: "; + resultItem.ExpectedTy.print(llvm::outs()); + llvm::outs() << "\n"; + + llvm::outs() << " TypeUSR: "; + printTypeUSR(resultItem.ExpectedTy, llvm::outs()); + llvm::outs() << "\n"; + + llvm::outs() << " ImplicitMembers:"; + if (resultItem.ImplicitMembers.empty()) + llvm::outs() << " []"; + llvm::outs() << "\n"; + for (auto VD : resultItem.ImplicitMembers) { + llvm::outs() << " - "; + + llvm::outs() << "Name: "; + VD->getName().print(llvm::outs()); + llvm::outs() << "\n"; + + StringRef BriefDoc = VD->getBriefComment(); + if (!BriefDoc.empty()) { + llvm::outs() << " DocBrief: \""; + llvm::outs() << VD->getBriefComment(); + llvm::outs() << "\"\n"; + } + } + } + llvm::outs() << "-----END TYPE CONTEXT INFO-----\n"; + return 0; + }); } static int doTypeContextInfo(const CompilerInvocation &InitInvok, @@ -915,18 +1008,20 @@ static int doTypeContextInfo(const CompilerInvocation &InitInvok, StringRef SecondSourceFileName, StringRef CodeCompletionToken, bool CodeCompletionDiagnostics) { - // Create a CodeCompletionConsumer. - std::unique_ptr Consumer( - new ide::PrintingTypeContextInfoConsumer(llvm::outs())); - - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeTypeContextInfoCallbacksFactory(*Consumer)); - - return doCodeCompletionImpl(callbacksFactory.get(), InitInvok, SourceFilename, - SecondSourceFileName, CodeCompletionToken, - CodeCompletionDiagnostics); + return performWithCompletionLikeOperationParams( + InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, + CodeCompletionDiagnostics, + [&](CompletionLikeOperationParams Params) -> bool { + CompletionInstance CompletionInst; + int ExitCode = 2; + CompletionInst.typeContextInfo( + Params.Invocation, Params.Args, Params.FileSystem, + Params.CompletionBuffer, Params.Offset, Params.DiagC, + [&](CancellableResult Result) { + ExitCode = printTypeContextInfo(Result); + }); + return ExitCode; + }); } static int From 974829e2908c2756c20e20ced5d94450d3fec3c4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 13:05:53 +0200 Subject: [PATCH 08/12] [swift-ide-test] Use dedicated method for conformingMethodList on CompletionInstance instead of generic performOperation --- include/swift/IDE/ConformingMethodList.h | 11 ---- lib/IDE/ConformingMethodList.cpp | 37 ------------ tools/swift-ide-test/swift-ide-test.cpp | 71 ++++++++++++++++++++---- 3 files changed, 59 insertions(+), 60 deletions(-) diff --git a/include/swift/IDE/ConformingMethodList.h b/include/swift/IDE/ConformingMethodList.h index 1dbe1c3770972..51865f4f743e5 100644 --- a/include/swift/IDE/ConformingMethodList.h +++ b/include/swift/IDE/ConformingMethodList.h @@ -44,17 +44,6 @@ class ConformingMethodListConsumer { virtual void handleResult(const ConformingMethodListResult &result) = 0; }; -/// Printing consumer -class PrintingConformingMethodListConsumer - : public ConformingMethodListConsumer { - llvm::raw_ostream &OS; - -public: - PrintingConformingMethodListConsumer(llvm::raw_ostream &OS) : OS(OS) {} - - void handleResult(const ConformingMethodListResult &result) override; -}; - /// Create a factory for code completion callbacks. CodeCompletionCallbacksFactory *makeConformingMethodListCallbacksFactory( ArrayRef expectedTypeNames, diff --git a/lib/IDE/ConformingMethodList.cpp b/lib/IDE/ConformingMethodList.cpp index 36d1e8f706cfc..53ee4c645c623 100644 --- a/lib/IDE/ConformingMethodList.cpp +++ b/lib/IDE/ConformingMethodList.cpp @@ -172,43 +172,6 @@ void ConformingMethodListCallbacks::getMatchingMethods( } // anonymous namespace. -void PrintingConformingMethodListConsumer::handleResult( - const ConformingMethodListResult &result) { - OS << "-----BEGIN CONFORMING METHOD LIST-----\n"; - - OS << "- TypeName: "; - result.ExprType.print(OS); - OS << "\n"; - - OS << "- Members: "; - if (result.Members.empty()) - OS << " []"; - OS << "\n"; - for (auto VD : result.Members) { - auto funcTy = cast(VD)->getMethodInterfaceType(); - funcTy = result.ExprType->getTypeOfMember(result.DC->getParentModule(), VD, - funcTy); - auto resultTy = funcTy->castTo()->getResult(); - - OS << " - Name: "; - VD->getName().print(OS); - OS << "\n"; - - OS << " TypeName: "; - resultTy.print(OS); - OS << "\n"; - - StringRef BriefDoc = VD->getBriefComment(); - if (!BriefDoc.empty()) { - OS << " DocBrief: \""; - OS << VD->getBriefComment(); - OS << "\"\n"; - } - } - - OS << "-----END CONFORMING METHOD LIST-----\n"; -} - CodeCompletionCallbacksFactory * swift::ide::makeConformingMethodListCallbacksFactory( ArrayRef expectedTypeNames, diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index eec12ecb1780a..69c882b10f236 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -1024,6 +1024,51 @@ static int doTypeContextInfo(const CompilerInvocation &InitInvok, }); } +static int printConformingMethodList( + CancellableResult CancellableResult) { + return printResult( + CancellableResult, [](ConformingMethodListResults &Results) { + auto Result = Results.Result; + if (!Result) { + return 0; + } + llvm::outs() << "-----BEGIN CONFORMING METHOD LIST-----\n"; + + llvm::outs() << "- TypeName: "; + Result->ExprType.print(llvm::outs()); + llvm::outs() << "\n"; + + llvm::outs() << "- Members: "; + if (Result->Members.empty()) + llvm::outs() << " []"; + llvm::outs() << "\n"; + for (auto VD : Result->Members) { + auto funcTy = cast(VD)->getMethodInterfaceType(); + funcTy = Result->ExprType->getTypeOfMember( + Result->DC->getParentModule(), VD, funcTy); + auto resultTy = funcTy->castTo()->getResult(); + + llvm::outs() << " - Name: "; + VD->getName().print(llvm::outs()); + llvm::outs() << "\n"; + + llvm::outs() << " TypeName: "; + resultTy.print(llvm::outs()); + llvm::outs() << "\n"; + + StringRef BriefDoc = VD->getBriefComment(); + if (!BriefDoc.empty()) { + llvm::outs() << " DocBrief: \""; + llvm::outs() << VD->getBriefComment(); + llvm::outs() << "\"\n"; + } + } + + llvm::outs() << "-----END CONFORMING METHOD LIST-----\n"; + return 0; + }); +} + static int doConformingMethodList(const CompilerInvocation &InitInvok, StringRef SourceFilename, StringRef SecondSourceFileName, @@ -1034,18 +1079,20 @@ doConformingMethodList(const CompilerInvocation &InitInvok, for (auto &name : expectedTypeNames) typeNames.push_back(name.c_str()); - // Create a CodeCompletionConsumer. - std::unique_ptr Consumer( - new ide::PrintingConformingMethodListConsumer(llvm::outs())); - - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeConformingMethodListCallbacksFactory(typeNames, *Consumer)); - - return doCodeCompletionImpl(callbacksFactory.get(), InitInvok, SourceFilename, - SecondSourceFileName, CodeCompletionToken, - CodeCompletionDiagnostics); + return performWithCompletionLikeOperationParams( + InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, + CodeCompletionDiagnostics, + [&](CompletionLikeOperationParams Params) -> bool { + CompletionInstance CompletionInst; + int ExitCode = 2; + CompletionInst.conformingMethodList( + Params.Invocation, Params.Args, Params.FileSystem, + Params.CompletionBuffer, Params.Offset, Params.DiagC, typeNames, + [&](CancellableResult Result) { + ExitCode = printConformingMethodList(Result); + }); + return ExitCode; + }); } static int doCodeCompletion(const CompilerInvocation &InitInvok, From 76f2dbe5e8e4de1a3cfa9832e7583410fb26e943 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Oct 2021 13:11:11 +0200 Subject: [PATCH 09/12] [swift-ide-test] Use dedicated method for code completion on CompletionInstance instead of generic performOperation --- include/swift/IDE/CodeCompletion.h | 26 --- lib/IDE/CodeCompletion.cpp | 75 -------- tools/swift-ide-test/swift-ide-test.cpp | 229 +++++++++++++----------- 3 files changed, 123 insertions(+), 207 deletions(-) diff --git a/include/swift/IDE/CodeCompletion.h b/include/swift/IDE/CodeCompletion.h index 0f0035c1e045d..f36e5a37c4601 100644 --- a/include/swift/IDE/CodeCompletion.h +++ b/include/swift/IDE/CodeCompletion.h @@ -1094,32 +1094,6 @@ struct SimpleCachingCodeCompletionConsumer : public CodeCompletionConsumer { virtual void handleResults(CodeCompletionContext &context) = 0; }; -/// A code completion result consumer that prints the results to a -/// \c raw_ostream. -class PrintingCodeCompletionConsumer - : public SimpleCachingCodeCompletionConsumer { - llvm::raw_ostream &OS; - bool IncludeKeywords; - bool IncludeComments; - bool IncludeSourceText; - bool PrintAnnotatedDescription; - -public: - PrintingCodeCompletionConsumer(llvm::raw_ostream &OS, - bool IncludeKeywords = true, - bool IncludeComments = true, - bool IncludeSourceText = false, - bool PrintAnnotatedDescription = false) - : OS(OS), - IncludeKeywords(IncludeKeywords), - IncludeComments(IncludeComments), - IncludeSourceText(IncludeSourceText), - PrintAnnotatedDescription(PrintAnnotatedDescription) {} - - void handleResults(CodeCompletionContext &context) override; - void handleResults(MutableArrayRef Results); -}; - /// Create a factory for code completion callbacks. CodeCompletionCallbacksFactory * makeCodeCompletionCallbacksFactory(CodeCompletionContext &CompletionContext, diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index d4e6a9f754e19..1c31a268a5b18 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -7406,81 +7406,6 @@ void CodeCompletionCallbacksImpl::doneParsing() { deliverCompletionResults(CompletionContext, Lookup, CurDeclContext, Consumer); } -void PrintingCodeCompletionConsumer::handleResults( - CodeCompletionContext &context) { - auto results = context.takeResults(); - handleResults(results); -} - -void PrintingCodeCompletionConsumer::handleResults( - MutableArrayRef Results) { - unsigned NumResults = 0; - for (auto Result : Results) { - if (!IncludeKeywords && Result->getKind() == CodeCompletionResult::Keyword) - continue; - ++NumResults; - } - if (NumResults == 0) - return; - - OS << "Begin completions, " << NumResults << " items\n"; - for (auto Result : Results) { - if (!IncludeKeywords && Result->getKind() == CodeCompletionResult::Keyword) - continue; - Result->printPrefix(OS); - if (PrintAnnotatedDescription) { - printCodeCompletionResultDescriptionAnnotated(*Result, OS, /*leadingPunctuation=*/false); - OS << "; typename="; - printCodeCompletionResultTypeNameAnnotated(*Result, OS); - } else { - Result->getCompletionString()->print(OS); - } - - OS << "; name="; - printCodeCompletionResultFilterName(*Result, OS); - - if (IncludeSourceText) { - OS << "; sourcetext="; - SmallString<64> buf; - { - llvm::raw_svector_ostream bufOS(buf); - printCodeCompletionResultSourceText(*Result, bufOS); - } - OS.write_escaped(buf); - } - - StringRef comment = Result->getBriefDocComment(); - if (IncludeComments && !comment.empty()) { - OS << "; comment=" << comment; - } - - if (Result->getDiagnosticSeverity() != - CodeCompletionDiagnosticSeverity::None) { - OS << "; diagnostics=" << comment; - switch (Result->getDiagnosticSeverity()) { - case CodeCompletionDiagnosticSeverity::Error: - OS << "error"; - break; - case CodeCompletionDiagnosticSeverity::Warning: - OS << "warning"; - break; - case CodeCompletionDiagnosticSeverity::Remark: - OS << "remark"; - break; - case CodeCompletionDiagnosticSeverity::Note: - OS << "note"; - break; - case CodeCompletionDiagnosticSeverity::None: - llvm_unreachable("none"); - } - OS << ":" << Result->getDiagnosticMessage(); - } - - OS << "\n"; - } - OS << "End completions\n"; -} - namespace { class CodeCompletionCallbacksFactoryImpl : public CodeCompletionCallbacksFactory { diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 69c882b10f236..2d3840297fa66 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -36,6 +36,7 @@ #include "swift/Frontend/PrintingDiagnosticConsumer.h" #include "swift/IDE/CompletionInstance.h" #include "swift/IDE/CodeCompletion.h" +#include "swift/IDE/CodeCompletionResultPrinter.h" #include "swift/IDE/CommentConversion.h" #include "swift/IDE/ConformingMethodList.h" #include "swift/IDE/ModuleInterfacePrinting.h" @@ -891,63 +892,6 @@ static bool performWithCompletionLikeOperationParams( return PerformOperation(Params); } -static bool -doCodeCompletionImpl(CodeCompletionCallbacksFactory *callbacksFactory, - const CompilerInvocation &InitInvok, - StringRef SourceFilename, StringRef SecondSourceFileName, - StringRef CodeCompletionToken, - bool CodeCompletionDiagnostics) { - return performWithCompletionLikeOperationParams( - InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, - CodeCompletionDiagnostics, - [&](CompletionLikeOperationParams Params) -> bool { - PrintingDiagnosticConsumer PrintDiags; - CompletionInstance CompletionInst; - std::string CompletionError; - bool CallbackCalled = false; - CompletionInst.performOperation( - Params.Invocation, Params.Args, Params.FileSystem, - Params.CompletionBuffer, Params.Offset, Params.DiagC, - [&](CancellableResult Result) { - CallbackCalled = true; - switch (Result.getKind()) { - case CancellableResultKind::Success: { - assert(!Result->DidReuseAST && - "reusing AST context without enabling it"); - - if (!Result->DidFindCodeCompletionToken) { - // Return empty results by not performing the second pass and - // never calling the consumer of the callback factory. - return; - } - - performCodeCompletionSecondPass( - *Result->CI.getCodeCompletionFile(), *callbacksFactory); - break; - } - case CancellableResultKind::Failure: - CompletionError = "error: " + Result.getError(); - break; - case CancellableResultKind::Cancelled: - CompletionError = "request cancelled"; - break; - } - }); - assert(CallbackCalled && - "Expected callback to be called synchronously"); - if (CompletionError == "error: did not find code completion token") { - // Do not consider failure to find the code completion token as a - // critical failure that returns a non-zero exit code. Instead, allow - // the caller to match the error message. - llvm::outs() << CompletionError << '\n'; - } else if (!CompletionError.empty()) { - llvm::errs() << CompletionError << '\n'; - return true; - } - return false; - }); -} - template static int printResult(CancellableResult Result, llvm::function_ref PrintSuccess) { @@ -1095,6 +1039,92 @@ doConformingMethodList(const CompilerInvocation &InitInvok, }); } +static void +printCodeCompletionResultsImpl(MutableArrayRef Results, + llvm::raw_ostream &OS, bool IncludeKeywords, + bool IncludeComments, bool IncludeSourceText, + bool PrintAnnotatedDescription) { + unsigned NumResults = 0; + for (auto Result : Results) { + if (!IncludeKeywords && Result->getKind() == CodeCompletionResult::Keyword) + continue; + ++NumResults; + } + if (NumResults == 0) + return; + + OS << "Begin completions, " << NumResults << " items\n"; + for (auto Result : Results) { + if (!IncludeKeywords && Result->getKind() == CodeCompletionResult::Keyword) + continue; + Result->printPrefix(OS); + if (PrintAnnotatedDescription) { + printCodeCompletionResultDescriptionAnnotated( + *Result, OS, /*leadingPunctuation=*/false); + OS << "; typename="; + printCodeCompletionResultTypeNameAnnotated(*Result, OS); + } else { + Result->getCompletionString()->print(OS); + } + + OS << "; name="; + printCodeCompletionResultFilterName(*Result, OS); + + if (IncludeSourceText) { + OS << "; sourcetext="; + SmallString<64> buf; + { + llvm::raw_svector_ostream bufOS(buf); + printCodeCompletionResultSourceText(*Result, bufOS); + } + OS.write_escaped(buf); + } + + StringRef comment = Result->getBriefDocComment(); + if (IncludeComments && !comment.empty()) { + OS << "; comment=" << comment; + } + + if (Result->getDiagnosticSeverity() != + CodeCompletionDiagnosticSeverity::None) { + OS << "; diagnostics=" << comment; + switch (Result->getDiagnosticSeverity()) { + case CodeCompletionDiagnosticSeverity::Error: + OS << "error"; + break; + case CodeCompletionDiagnosticSeverity::Warning: + OS << "warning"; + break; + case CodeCompletionDiagnosticSeverity::Remark: + OS << "remark"; + break; + case CodeCompletionDiagnosticSeverity::Note: + OS << "note"; + break; + case CodeCompletionDiagnosticSeverity::None: + llvm_unreachable("none"); + } + OS << ":" << Result->getDiagnosticMessage(); + } + + OS << "\n"; + } + OS << "End completions\n"; +} + +static int printCodeCompletionResults( + CancellableResult CancellableResult, + bool IncludeKeywords, bool IncludeComments, bool IncludeSourceText, + bool PrintAnnotatedDescription) { + return printResult( + CancellableResult, [&](CodeCompleteResult &Result) { + printCodeCompletionResultsImpl( + Result.Results, llvm::outs(), IncludeKeywords, IncludeComments, + IncludeSourceText, PrintAnnotatedDescription); + return 0; + }); +} + static int doCodeCompletion(const CompilerInvocation &InitInvok, StringRef SourceFilename, StringRef SecondSourceFileName, @@ -1117,20 +1147,23 @@ static int doCodeCompletion(const CompilerInvocation &InitInvok, CompletionContext.setAddInitsToTopLevel(CodeCompletionAddInitsToTopLevel); CompletionContext.setCallPatternHeuristics(CodeCompletionCallPatternHeuristics); - // Create a CodeCompletionConsumer. - std::unique_ptr Consumer( - new ide::PrintingCodeCompletionConsumer( - llvm::outs(), CodeCompletionKeywords, CodeCompletionComments, - CodeCompletionSourceText, CodeCompletionAnnotateResults)); - - // Create a factory for code completion callbacks that will feed the - // Consumer. - std::unique_ptr callbacksFactory( - ide::makeCodeCompletionCallbacksFactory(CompletionContext, *Consumer)); - - return doCodeCompletionImpl(callbacksFactory.get(), InitInvok, SourceFilename, - SecondSourceFileName, CodeCompletionToken, - CodeCompletionDiagnostics); + return performWithCompletionLikeOperationParams( + InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, + CodeCompletionDiagnostics, + [&](CompletionLikeOperationParams Params) -> bool { + CompletionInstance Inst; + int ExitCode = 2; + Inst.codeComplete( + Params.Invocation, Params.Args, Params.FileSystem, + Params.CompletionBuffer, Params.Offset, Params.DiagC, + std::move(CompletionContext), + [&](CancellableResult Result) { + ExitCode = printCodeCompletionResults( + Result, CodeCompletionKeywords, CodeCompletionComments, + CodeCompletionSourceText, CodeCompletionAnnotateResults); + }); + return ExitCode; + }); } namespace { @@ -1438,45 +1471,30 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, auto completionBuffer = ide::makeCodeCompletionMemoryBuffer( CleanFile.get(), Offset, CleanFile->getBufferIdentifier()); + ide::CodeCompletionContext CompletionContext(CompletionCache); + CompletionContext.setAnnotateResult(CodeCompletionAnnotateResults); + CompletionContext.setAddInitsToTopLevel(CodeCompletionAddInitsToTopLevel); + CompletionContext.setCallPatternHeuristics( + CodeCompletionCallPatternHeuristics); + PrintingDiagnosticConsumer PrintDiags; auto completionStart = std::chrono::high_resolution_clock::now(); bool wasASTContextReused = false; std::string completionError = ""; bool CallbackCalled = false; - CompletionInst.performOperation( + CompletionInst.codeComplete( Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr, - [&](CancellableResult Result) { + std::move(CompletionContext), + [&](CancellableResult Result) { CallbackCalled = true; switch (Result.getKind()) { case CancellableResultKind::Success: { - if (!Result->DidFindCodeCompletionToken) { - // Return empty results by not performing the second pass and - // never calling the consumer of the callback factory. - return; - } - wasASTContextReused = Result->DidReuseAST; - - // Create a CodeCompletionConsumer. - std::unique_ptr Consumer( - new ide::PrintingCodeCompletionConsumer( - OS, IncludeKeywords, IncludeComments, - CodeCompletionAnnotateResults, IncludeSourceText)); - - // Create a factory for code completion callbacks that will feed the - // Consumer. - ide::CodeCompletionContext CompletionContext(CompletionCache); - CompletionContext.setAnnotateResult(CodeCompletionAnnotateResults); - CompletionContext.setAddInitsToTopLevel( - CodeCompletionAddInitsToTopLevel); - CompletionContext.setCallPatternHeuristics( - CodeCompletionCallPatternHeuristics); - std::unique_ptr callbacksFactory( - ide::makeCodeCompletionCallbacksFactory(CompletionContext, - *Consumer)); - - performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(), - *callbacksFactory); + wasASTContextReused = + Result->Info.completionContext->ReusingASTContext; + printCodeCompletionResultsImpl(Result->Results, OS, IncludeKeywords, + IncludeComments, IncludeSourceText, + CodeCompletionAnnotateResults); break; } case CancellableResultKind::Failure: @@ -4045,18 +4063,17 @@ int main(int argc, char *argv[]) { return 1; } - ide::PrintingCodeCompletionConsumer Consumer( - llvm::outs(), options::CodeCompletionKeywords, - options::CodeCompletionComments, - options::CodeCompletionSourceText, - options::CodeCompletionAnnotateResults); for (StringRef filename : options::InputFilenames) { auto resultsOpt = ide::OnDiskCodeCompletionCache::getFromFile(filename); if (!resultsOpt) { // FIXME: error? continue; } - Consumer.handleResults(resultsOpt->get()->Sink.Results); + printCodeCompletionResultsImpl( + resultsOpt->get()->Sink.Results, llvm::outs(), + options::CodeCompletionKeywords, options::CodeCompletionComments, + options::CodeCompletionSourceText, + options::CodeCompletionAnnotateResults); } return 0; From 6b66d58a3e294661fe7a0dddc4a4edd4073f3470 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 12 Oct 2021 23:02:49 +0200 Subject: [PATCH 10/12] [SourceKit] Make CompletionInstance::performOperation private All users of `CompletionInstance::performOperation` have been migrated to dedicated methods. `performOperation` with its callback that needs to invoke the second pass is now an implementation detail of `CompletionInstance`. --- include/swift/IDE/CompletionInstance.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index 28eef54639a18..f1fa411353d4f 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -122,16 +122,6 @@ class CompletionInstance { llvm::function_ref)> Callback); -public: - CompletionInstance() : CachedCIShouldBeInvalidated(false) {} - - // Mark the cached compiler instance "should be invalidated". In the next - // completion, new compiler instance will be used. (Thread safe.) - void markCachedCompilerInstanceShouldBeInvalidated(); - - // Update options with \c NewOpts. (Thread safe.) - void setOptions(Options NewOpts); - /// Calls \p Callback with a \c CompilerInstance which is prepared for the /// second pass. \p Callback is resposible to perform the second pass on it. /// The \c CompilerInstance may be reused from the previous completions, @@ -150,6 +140,16 @@ class CompletionInstance { llvm::function_ref)> Callback); +public: + CompletionInstance() : CachedCIShouldBeInvalidated(false) {} + + // Mark the cached compiler instance "should be invalidated". In the next + // completion, new compiler instance will be used. (Thread safe.) + void markCachedCompilerInstanceShouldBeInvalidated(); + + // Update options with \c NewOpts. (Thread safe.) + void setOptions(Options NewOpts); + void codeComplete( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, From 95ae8a41cc1cbe2c73b77de05793891b67be8df8 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 26 Oct 2021 15:31:25 +0200 Subject: [PATCH 11/12] [SourceKit] Make completion-like helper functions static --- .../lib/SwiftLang/SwiftCompletion.cpp | 20 ++++--------------- .../SwiftLang/SwiftConformingMethodList.cpp | 5 +++-- .../lib/SwiftLang/SwiftTypeContextInfo.cpp | 4 ++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp index 87a1782ddc3b1..bb631b579a6ec 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp @@ -92,19 +92,6 @@ static UIdent getUIDForCodeCompletionKindToReport(CompletionKind kind) { } } -/// The result returned via the Callback of \c swiftCodeCompleteImpl. -/// If \c HasResults is \c false, code completion did not fail, but did not -/// produce any values either. All other fields of the struct should be ignored -/// in that case. -struct CodeCompleteImplResult { - bool HasResults; - swift::ASTContext *Context; - const swift::CompilerInvocation *Invocation; - swift::ide::CodeCompletionContext *CompletionContext; - ArrayRef RequestedModules; - DeclContext *DC; -}; - static void swiftCodeCompleteImpl( SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, ArrayRef Args, @@ -141,9 +128,10 @@ static void translateCodeCompletionOptions(OptionsDictionary &from, unsigned &resultOffset, unsigned &maxResults); -void deliverCodeCompleteResults(SourceKit::CodeCompletionConsumer &SKConsumer, - const CodeCompletion::Options &CCOpts, - CancellableResult Result) { +static void +deliverCodeCompleteResults(SourceKit::CodeCompletionConsumer &SKConsumer, + const CodeCompletion::Options &CCOpts, + CancellableResult Result) { switch (Result.getKind()) { case CancellableResultKind::Success: { auto kind = getUIDForCodeCompletionKindToReport( diff --git a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp index 111b893039db4..3f3a1320c84f4 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp @@ -32,8 +32,9 @@ translateConformingMethodListOptions(OptionsDictionary &from, // ConformingMethodList doesn't receive any options at this point. } -void deliverResults(SourceKit::ConformingMethodListConsumer &SKConsumer, - CancellableResult Result) { +static void +deliverResults(SourceKit::ConformingMethodListConsumer &SKConsumer, + CancellableResult Result) { switch (Result.getKind()) { case CancellableResultKind::Success: { SKConsumer.setReusingASTContext(Result->DidReuseAST); diff --git a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp index a756d96db25e1..63384a1043706 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp @@ -30,8 +30,8 @@ static void translateTypeContextInfoOptions(OptionsDictionary &from, // TypeContextInfo doesn't receive any options at this point. } -void deliverResults(SourceKit::TypeContextInfoConsumer &SKConsumer, - CancellableResult Result) { +static void deliverResults(SourceKit::TypeContextInfoConsumer &SKConsumer, + CancellableResult Result) { switch (Result.getKind()) { case CancellableResultKind::Success: { SKConsumer.setReusingASTContext(Result->DidReuseAST); From c9f53318044a7f348e2c32c2f6cadfd4b271b978 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 29 Oct 2021 11:57:56 +0200 Subject: [PATCH 12/12] [SourceKit] Pass CompletionContext by reference to CompletionInstance --- include/swift/IDE/CompletionInstance.h | 2 +- lib/IDE/CompletionInstance.cpp | 2 +- tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp | 2 +- tools/swift-ide-test/swift-ide-test.cpp | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/swift/IDE/CompletionInstance.h b/include/swift/IDE/CompletionInstance.h index f1fa411353d4f..0c9e3b1aa7e82 100644 --- a/include/swift/IDE/CompletionInstance.h +++ b/include/swift/IDE/CompletionInstance.h @@ -154,7 +154,7 @@ class CompletionInstance { swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, - DiagnosticConsumer *DiagC, ide::CodeCompletionContext &&CompletionContext, + DiagnosticConsumer *DiagC, ide::CodeCompletionContext &CompletionContext, llvm::function_ref)> Callback); void typeContextInfo( diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index 703e35b7ffb8f..3c59acc414117 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -661,7 +661,7 @@ void swift::ide::CompletionInstance::codeComplete( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, llvm::MemoryBuffer *completionBuffer, unsigned int Offset, - DiagnosticConsumer *DiagC, ide::CodeCompletionContext &&CompletionContext, + DiagnosticConsumer *DiagC, ide::CodeCompletionContext &CompletionContext, llvm::function_ref)> Callback) { using ResultType = CancellableResult; diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp index bb631b579a6ec..4dc81024e0320 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp @@ -116,7 +116,7 @@ static void swiftCodeCompleteImpl( Lang.getCompletionInstance()->codeComplete( CIParams.Invocation, Args, FileSystem, CIParams.completionBuffer, Offset, CIParams.DiagC, - std::move(CompletionContext), DeliverTransformed); + CompletionContext, DeliverTransformed); }, Callback); }); diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 2d3840297fa66..2150f7d8b82fa 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -1156,7 +1156,7 @@ static int doCodeCompletion(const CompilerInvocation &InitInvok, Inst.codeComplete( Params.Invocation, Params.Args, Params.FileSystem, Params.CompletionBuffer, Params.Offset, Params.DiagC, - std::move(CompletionContext), + CompletionContext, [&](CancellableResult Result) { ExitCode = printCodeCompletionResults( Result, CodeCompletionKeywords, CodeCompletionComments, @@ -1484,8 +1484,7 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, bool CallbackCalled = false; CompletionInst.codeComplete( Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset, - CodeCompletionDiagnostics ? &PrintDiags : nullptr, - std::move(CompletionContext), + CodeCompletionDiagnostics ? &PrintDiags : nullptr, CompletionContext, [&](CancellableResult Result) { CallbackCalled = true; switch (Result.getKind()) {