Skip to content

Commit

Permalink
[clangd] Add fallback mode for code completion when compile command o…
Browse files Browse the repository at this point in the history
…r preamble is not ready.

Summary:
When calling TUScehduler::runWithPreamble (e.g. in code compleiton), allow
entering a fallback mode when compile command or preamble is not ready, instead of
waiting. This allows clangd to perform naive code completion e.g. using identifiers
in the current file or symbols in the index.

This patch simply returns empty result for code completion in fallback mode. Identifier-based
plus more advanced index-based completion will be added in followup patches.

Reviewers: ilya-biryukov, sammccall

Reviewed By: sammccall

Subscribers: sammccall, javed.absar, MaskRay, jkorous, arphaman, kadircet, jdoerfert, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D59811

llvm-svn: 357916
  • Loading branch information
Eric Liu committed Apr 8, 2019
1 parent c70d38e commit dd02825
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 17 deletions.
20 changes: 19 additions & 1 deletion clang-tools-extra/clangd/ClangdServer.cpp
Expand Up @@ -11,7 +11,9 @@
#include "CodeComplete.h"
#include "FindSymbols.h"
#include "Headers.h"
#include "Protocol.h"
#include "SourceCode.h"
#include "TUScheduler.h"
#include "Trace.h"
#include "index/CanonicalIncludes.h"
#include "index/FileIndex.h"
Expand All @@ -21,6 +23,7 @@
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
Expand Down Expand Up @@ -152,6 +155,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
if (ClangTidyOptProvider)
Opts.ClangTidyOpts = ClangTidyOptProvider->getOptions(File);
Opts.SuggestMissingIncludes = SuggestMissingIncludes;

// FIXME: some build systems like Bazel will take time to preparing
// environment to build the file, it would be nice if we could emit a
// "PreparingBuild" status to inform users, it is non-trivial given the
Expand Down Expand Up @@ -183,6 +187,18 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
return CB(IP.takeError());
if (isCancelled())
return CB(llvm::make_error<CancelledError>());
if (!IP->Preamble) {
vlog("File {0} is not ready for code completion. Enter fallback mode.",
File);
CodeCompleteResult CCR;
CCR.Context = CodeCompletionContext::CCC_Recovery;

// FIXME: perform simple completion e.g. using identifiers in the current
// file and symbols in the index.
// FIXME: let clients know that we've entered fallback mode.

return CB(std::move(CCR));
}

llvm::Optional<SpeculativeFuzzyFind> SpecFuzzyFind;
if (CodeCompleteOpts.Index && CodeCompleteOpts.SpeculativeIndexRequest) {
Expand Down Expand Up @@ -214,7 +230,9 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
};

// We use a potentially-stale preamble because latency is critical here.
WorkScheduler.runWithPreamble("CodeComplete", File, TUScheduler::Stale,
WorkScheduler.runWithPreamble("CodeComplete", File,
Opts.AllowFallback ? TUScheduler::StaleOrAbsent
: TUScheduler::Stale,
Bind(Task, File.str(), std::move(CB)));
}

Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/CodeComplete.h
Expand Up @@ -109,6 +109,12 @@ struct CodeCompleteOptions {
///
/// Such completions can insert scope qualifiers.
bool AllScopes = false;

/// Whether to allow falling back to code completion without compiling files
/// (using identifiers in the current file and symbol indexes), when file
/// cannot be built (e.g. missing compile command), or the build is not ready
/// (e.g. preamble is still being built).
bool AllowFallback = false;
};

// Semi-structured representation of a code-complete suggestion for our C++ API.
Expand Down
29 changes: 16 additions & 13 deletions clang-tools-extra/clangd/TUScheduler.cpp
Expand Up @@ -47,6 +47,7 @@
#include "Trace.h"
#include "index/CanonicalIncludes.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Path.h"
Expand Down Expand Up @@ -179,6 +180,7 @@ class ASTWorker {
bool blockUntilIdle(Deadline Timeout) const;

std::shared_ptr<const PreambleData> getPossiblyStalePreamble() const;

/// Obtain a preamble reflecting all updates so far. Threadsafe.
/// It may be delivered immediately, or later on the worker thread.
void getCurrentPreamble(
Expand Down Expand Up @@ -242,7 +244,6 @@ class ASTWorker {
/// Whether the diagnostics for the current FileInputs were reported to the
/// users before.
bool DiagsWereReported = false;
/// Size of the last AST
/// Guards members used by both TUScheduler and the worker thread.
mutable std::mutex Mutex;
std::shared_ptr<const PreambleData> LastBuiltPreamble; /* GUARDED_BY(Mutex) */
Expand Down Expand Up @@ -858,9 +859,9 @@ void TUScheduler::runWithAST(
It->second->Worker->runWithAST(Name, std::move(Action));
}

void TUScheduler::runWithPreamble(
llvm::StringRef Name, PathRef File, PreambleConsistency Consistency,
llvm::unique_function<void(llvm::Expected<InputsAndPreamble>)> Action) {
void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
PreambleConsistency Consistency,
Callback<InputsAndPreamble> Action) {
auto It = Files.find(File);
if (It == Files.end()) {
Action(llvm::make_error<LSPError>(
Expand Down Expand Up @@ -893,19 +894,21 @@ void TUScheduler::runWithPreamble(
}

std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
auto Task = [Worker, this](std::string Name, std::string File,
std::string Contents,
tooling::CompileCommand Command, Context Ctx,
decltype(ConsistentPreamble) ConsistentPreamble,
decltype(Action) Action) mutable {
auto Task = [Worker, Consistency,
this](std::string Name, std::string File, std::string Contents,
tooling::CompileCommand Command, Context Ctx,
decltype(ConsistentPreamble) ConsistentPreamble,
decltype(Action) Action) mutable {
std::shared_ptr<const PreambleData> Preamble;
if (ConsistentPreamble.valid()) {
Preamble = ConsistentPreamble.get();
} else {
// We don't want to be running preamble actions before the preamble was
// built for the first time. This avoids extra work of processing the
// preamble headers in parallel multiple times.
Worker->waitForFirstPreamble();
if (Consistency != PreambleConsistency::StaleOrAbsent) {
// Wait until the preamble is built for the first time, if preamble is
// required. This avoids extra work of processing the preamble headers
// in parallel multiple times.
Worker->waitForFirstPreamble();
}
Preamble = Worker->getPossiblyStalePreamble();
}

Expand Down
11 changes: 9 additions & 2 deletions clang-tools-extra/clangd/TUScheduler.h
Expand Up @@ -13,7 +13,9 @@
#include "Function.h"
#include "Threading.h"
#include "index/CanonicalIncludes.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include <future>

namespace clang {
Expand All @@ -32,6 +34,7 @@ struct InputsAndAST {
struct InputsAndPreamble {
llvm::StringRef Contents;
const tooling::CompileCommand &Command;
// This can be nullptr if no preamble is availble.
const PreambleData *Preamble;
};

Expand Down Expand Up @@ -178,10 +181,14 @@ class TUScheduler {
/// reading source code from headers.
/// This is the fastest option, usually a preamble is available immediately.
Stale,
/// Besides accepting stale preamble, this also allow preamble to be absent
/// (not ready or failed to build).
StaleOrAbsent,
};

/// Schedule an async read of the preamble.
/// If there's no preamble yet (because the file was just opened), we'll wait
/// for it to build. The result may be null if it fails to build or is empty.
/// If there's no up-to-date preamble, we follow the PreambleConsistency
/// policy.
/// If an error occurs, it is forwarded to the \p Action callback.
/// Context cancellation is ignored and should be handled by the Action.
/// (In practice, the Action is almost always executed immediately).
Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Expand Up @@ -231,6 +231,14 @@ static llvm::cl::opt<OffsetEncoding> ForceOffsetEncoding(
"Offsets are in UTF-16 code units")),
llvm::cl::init(OffsetEncoding::UnsupportedEncoding));

static llvm::cl::opt<bool> AllowFallbackCompletion(
"allow-fallback-completion",
llvm::cl::desc(
"Allow falling back to code completion without compiling files (using "
"identifiers and symbol indexes), when file cannot be built or the "
"build is not ready."),
llvm::cl::init(false));

namespace {

/// \brief Supports a test URI scheme with relaxed constraints for lit tests.
Expand Down Expand Up @@ -437,6 +445,7 @@ int main(int argc, char *argv[]) {
CCOpts.SpeculativeIndexRequest = Opts.StaticIndex;
CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets;
CCOpts.AllScopes = AllScopesCompletion;
CCOpts.AllowFallback = AllowFallbackCompletion;

RealFileSystemProvider FSProvider;
// Initialize and run ClangdLSPServer.
Expand Down
38 changes: 37 additions & 1 deletion clang-tools-extra/unittests/clangd/ClangdTests.cpp
Expand Up @@ -13,8 +13,10 @@
#include "Matchers.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "Threading.h"
#include "URI.h"
#include "clang/Config/config.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Errc.h"
Expand All @@ -36,7 +38,6 @@ namespace clangd {
namespace {

using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::IsEmpty;
Expand Down Expand Up @@ -1058,6 +1059,41 @@ TEST_F(ClangdVFSTest, FlagsWithPlugins) {
EXPECT_NE(Result, "<no-ast>");
}

TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) {
MockFSProvider FS;
ErrorCheckingDiagConsumer DiagConsumer;
MockCompilationDatabase CDB;
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());

auto FooCpp = testPath("foo.cpp");
Annotations Code(R"cpp(
int main() {
int xyz;
xy^
})cpp");
FS.Files[FooCpp] = FooCpp;

auto Opts = clangd::CodeCompleteOptions();
Opts.AllowFallback = true;

// This will make compile command broken and preamble absent.
CDB.ExtraClangFlags = {"yolo.cc"};
Server.addDocument(FooCpp, Code.code());
ASSERT_TRUE(Server.blockUntilIdleForTest());
auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts));
EXPECT_THAT(Res.Completions, IsEmpty());
EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);

// Make the compile command work again.
CDB.ExtraClangFlags = {"-std=c++11"};
Server.addDocument(FooCpp, Code.code());
ASSERT_TRUE(Server.blockUntilIdleForTest());
EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(),
Opts))
.Completions,
ElementsAre(Field(&CodeCompletion::Name, "xyz")));
}

} // namespace
} // namespace clangd
} // namespace clang

0 comments on commit dd02825

Please sign in to comment.