summaryrefslogtreecommitdiffstats
path: root/clangd/CodeComplete.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clangd/CodeComplete.cpp')
-rw-r--r--clangd/CodeComplete.cpp590
1 files changed, 359 insertions, 231 deletions
diff --git a/clangd/CodeComplete.cpp b/clangd/CodeComplete.cpp
index 7642ea9d..4789a809 100644
--- a/clangd/CodeComplete.cpp
+++ b/clangd/CodeComplete.cpp
@@ -1,9 +1,8 @@
//===--- CodeComplete.cpp ----------------------------------------*- C++-*-===//
//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
@@ -35,20 +34,29 @@
#include "Trace.h"
#include "URI.h"
#include "index/Index.h"
+#include "index/Symbol.h"
+#include "index/SymbolOrigin.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
+#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
+#include "clang/Lex/ExternalPreprocessorSource.h"
+#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Sema/CodeCompleteConsumer.h"
+#include "clang/Sema/DeclSpec.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Debug.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
@@ -178,27 +186,11 @@ std::string getOptionalParameters(const CodeCompletionString &CCS,
return Result;
}
-/// Creates a `HeaderFile` from \p Header which can be either a URI or a literal
-/// include.
-static llvm::Expected<HeaderFile> toHeaderFile(llvm::StringRef Header,
- llvm::StringRef HintPath) {
- if (isLiteralInclude(Header))
- return HeaderFile{Header.str(), /*Verbatim=*/true};
- auto U = URI::parse(Header);
- if (!U)
- return U.takeError();
-
- auto IncludePath = URI::includeSpelling(*U);
- if (!IncludePath)
- return IncludePath.takeError();
- if (!IncludePath->empty())
- return HeaderFile{std::move(*IncludePath), /*Verbatim=*/true};
-
- auto Resolved = URI::resolve(*U, HintPath);
- if (!Resolved)
- return Resolved.takeError();
- return HeaderFile{std::move(*Resolved), /*Verbatim=*/false};
-}
+// Identifier code completion result.
+struct RawIdentifier {
+ llvm::StringRef Name;
+ unsigned References; // # of usages in file.
+};
/// A code completion result, in clang-native form.
/// It may be promoted to a CompletionItem if it's among the top-ranked results.
@@ -207,11 +199,14 @@ struct CompletionCandidate {
// We may have a result from Sema, from the index, or both.
const CodeCompletionResult *SemaResult = nullptr;
const Symbol *IndexResult = nullptr;
+ const RawIdentifier *IdentifierResult = nullptr;
llvm::SmallVector<llvm::StringRef, 1> RankedIncludeHeaders;
// Returns a token identifying the overload set this is part of.
// 0 indicates it's not part of any overload set.
- size_t overloadSet() const {
+ size_t overloadSet(const CodeCompleteOptions &Opts) const {
+ if (!Opts.BundleOverloads)
+ return 0;
llvm::SmallString<256> Scratch;
if (IndexResult) {
switch (IndexResult->SymInfo.Kind) {
@@ -228,27 +223,32 @@ struct CompletionCandidate {
// This could break #include insertion.
return llvm::hash_combine(
(IndexResult->Scope + IndexResult->Name).toStringRef(Scratch),
- headerToInsertIfAllowed().getValueOr(""));
+ headerToInsertIfAllowed(Opts).getValueOr(""));
default:
return 0;
}
}
- assert(SemaResult);
- // We need to make sure we're consistent with the IndexResult case!
- const NamedDecl *D = SemaResult->Declaration;
- if (!D || !D->isFunctionOrFunctionTemplate())
- return 0;
- {
- llvm::raw_svector_ostream OS(Scratch);
- D->printQualifiedName(OS);
+ if (SemaResult) {
+ // We need to make sure we're consistent with the IndexResult case!
+ const NamedDecl *D = SemaResult->Declaration;
+ if (!D || !D->isFunctionOrFunctionTemplate())
+ return 0;
+ {
+ llvm::raw_svector_ostream OS(Scratch);
+ D->printQualifiedName(OS);
+ }
+ return llvm::hash_combine(Scratch,
+ headerToInsertIfAllowed(Opts).getValueOr(""));
}
- return llvm::hash_combine(Scratch,
- headerToInsertIfAllowed().getValueOr(""));
+ assert(IdentifierResult);
+ return 0;
}
// The best header to include if include insertion is allowed.
- llvm::Optional<llvm::StringRef> headerToInsertIfAllowed() const {
- if (RankedIncludeHeaders.empty())
+ llvm::Optional<llvm::StringRef>
+ headerToInsertIfAllowed(const CodeCompleteOptions &Opts) const {
+ if (Opts.InsertIncludes == CodeCompleteOptions::NeverInsert ||
+ RankedIncludeHeaders.empty())
return None;
if (SemaResult && SemaResult->Declaration) {
// Avoid inserting new #include if the declaration is found in the current
@@ -282,7 +282,7 @@ struct ScoredBundleGreater {
// computed from the first candidate, in the constructor.
// Others vary per candidate, so add() must be called for remaining candidates.
struct CodeCompletionBuilder {
- CodeCompletionBuilder(ASTContext &ASTCtx, const CompletionCandidate &C,
+ CodeCompletionBuilder(ASTContext *ASTCtx, const CompletionCandidate &C,
CodeCompletionString *SemaCCS,
llvm::ArrayRef<std::string> QueryScopes,
const IncludeInserter &Includes,
@@ -293,6 +293,7 @@ struct CodeCompletionBuilder {
EnableFunctionArgSnippets(Opts.EnableFunctionArgSnippets) {
add(C, SemaCCS);
if (C.SemaResult) {
+ assert(ASTCtx);
Completion.Origin |= SymbolOrigin::AST;
Completion.Name = llvm::StringRef(SemaCCS->getTypedText());
if (Completion.Scope.empty()) {
@@ -311,8 +312,8 @@ struct CodeCompletionBuilder {
Completion.Name.back() == '/')
Completion.Kind = CompletionItemKind::Folder;
for (const auto &FixIt : C.SemaResult->FixIts) {
- Completion.FixIts.push_back(
- toTextEdit(FixIt, ASTCtx.getSourceManager(), ASTCtx.getLangOpts()));
+ Completion.FixIts.push_back(toTextEdit(
+ FixIt, ASTCtx->getSourceManager(), ASTCtx->getLangOpts()));
}
llvm::sort(Completion.FixIts, [](const TextEdit &X, const TextEdit &Y) {
return std::tie(X.range.start.line, X.range.start.character) <
@@ -343,22 +344,30 @@ struct CodeCompletionBuilder {
}
Completion.Deprecated |= (C.IndexResult->Flags & Symbol::Deprecated);
}
+ if (C.IdentifierResult) {
+ Completion.Origin |= SymbolOrigin::Identifier;
+ Completion.Kind = CompletionItemKind::Text;
+ Completion.Name = C.IdentifierResult->Name;
+ }
// Turn absolute path into a literal string that can be #included.
auto Inserted = [&](llvm::StringRef Header)
-> llvm::Expected<std::pair<std::string, bool>> {
- auto ResolvedDeclaring =
- toHeaderFile(C.IndexResult->CanonicalDeclaration.FileURI, FileName);
+ auto DeclaringURI =
+ URI::parse(C.IndexResult->CanonicalDeclaration.FileURI);
+ if (!DeclaringURI)
+ return DeclaringURI.takeError();
+ auto ResolvedDeclaring = URI::resolve(*DeclaringURI, FileName);
if (!ResolvedDeclaring)
return ResolvedDeclaring.takeError();
auto ResolvedInserted = toHeaderFile(Header, FileName);
if (!ResolvedInserted)
return ResolvedInserted.takeError();
return std::make_pair(
- Includes.calculateIncludePath(*ResolvedDeclaring, *ResolvedInserted),
+ Includes.calculateIncludePath(*ResolvedInserted),
Includes.shouldInsertInclude(*ResolvedDeclaring, *ResolvedInserted));
};
- bool ShouldInsert = C.headerToInsertIfAllowed().hasValue();
+ bool ShouldInsert = C.headerToInsertIfAllowed(Opts).hasValue();
// Calculate include paths and edits for all possible headers.
for (const auto &Inc : C.RankedIncludeHeaders) {
if (auto ToInclude = Inserted(Inc)) {
@@ -397,7 +406,7 @@ struct CodeCompletionBuilder {
if (C.IndexResult)
Completion.Documentation = C.IndexResult->Documentation;
else if (C.SemaResult)
- Completion.Documentation = getDocComment(ASTCtx, *C.SemaResult,
+ Completion.Documentation = getDocComment(*ASTCtx, *C.SemaResult,
/*CommentsFromHeader=*/false);
}
}
@@ -492,7 +501,8 @@ private:
return "(…)";
}
- ASTContext &ASTCtx;
+ // ASTCtx can be nullptr if not run with sema.
+ ASTContext *ASTCtx;
CodeCompletion Completion;
llvm::SmallVector<BundledEntry, 1> Bundled;
bool ExtractDocumentation;
@@ -549,7 +559,7 @@ struct SpecifiedScope {
std::set<std::string> Results;
for (llvm::StringRef AS : AccessibleScopes)
Results.insert(
- ((UnresolvedQualifier ? *UnresolvedQualifier : "") + AS).str());
+ (AS + (UnresolvedQualifier ? *UnresolvedQualifier : "")).str());
return {Results.begin(), Results.end()};
}
};
@@ -559,55 +569,59 @@ struct SpecifiedScope {
// (e.g. enclosing namespace).
std::pair<std::vector<std::string>, bool>
getQueryScopes(CodeCompletionContext &CCContext, const Sema &CCSema,
+ const CompletionPrefix &HeuristicPrefix,
const CodeCompleteOptions &Opts) {
- auto GetAllAccessibleScopes = [](CodeCompletionContext &CCContext) {
- SpecifiedScope Info;
- for (auto *Context : CCContext.getVisitedContexts()) {
- if (isa<TranslationUnitDecl>(Context))
- Info.AccessibleScopes.push_back(""); // global namespace
- else if (isa<NamespaceDecl>(Context))
- Info.AccessibleScopes.push_back(printNamespaceScope(*Context));
- }
- return Info;
- };
-
- auto SS = CCContext.getCXXScopeSpecifier();
+ SpecifiedScope Scopes;
+ for (auto *Context : CCContext.getVisitedContexts()) {
+ if (isa<TranslationUnitDecl>(Context))
+ Scopes.AccessibleScopes.push_back(""); // global namespace
+ else if (isa<NamespaceDecl>(Context))
+ Scopes.AccessibleScopes.push_back(printNamespaceScope(*Context));
+ }
- // Unqualified completion (e.g. "vec^").
- if (!SS) {
- std::vector<std::string> Scopes;
+ const CXXScopeSpec *SemaSpecifier =
+ CCContext.getCXXScopeSpecifier().getValueOr(nullptr);
+ // Case 1: unqualified completion.
+ if (!SemaSpecifier) {
+ // Case 2 (exception): sema saw no qualifier, but there appears to be one!
+ // This can happen e.g. in incomplete macro expansions. Use heuristics.
+ if (!HeuristicPrefix.Qualifier.empty()) {
+ vlog("Sema said no scope specifier, but we saw {0} in the source code",
+ HeuristicPrefix.Qualifier);
+ StringRef SpelledSpecifier = HeuristicPrefix.Qualifier;
+ if (SpelledSpecifier.consume_front("::"))
+ Scopes.AccessibleScopes = {""};
+ Scopes.UnresolvedQualifier = SpelledSpecifier;
+ return {Scopes.scopesForIndexQuery(), false};
+ }
+ // The enclosing namespace must be first, it gets a quality boost.
+ std::vector<std::string> EnclosingAtFront;
std::string EnclosingScope = printNamespaceScope(*CCSema.CurContext);
- Scopes.push_back(EnclosingScope);
- for (auto &S : GetAllAccessibleScopes(CCContext).scopesForIndexQuery()) {
+ EnclosingAtFront.push_back(EnclosingScope);
+ for (auto &S : Scopes.scopesForIndexQuery()) {
if (EnclosingScope != S)
- Scopes.push_back(std::move(S));
+ EnclosingAtFront.push_back(std::move(S));
}
- // Allow AllScopes completion only for there is no explicit scope qualifier.
- return {Scopes, Opts.AllScopes};
+ // Allow AllScopes completion as there is no explicit scope qualifier.
+ return {EnclosingAtFront, Opts.AllScopes};
}
-
- // Qualified completion ("std::vec^"), we have two cases depending on whether
- // the qualifier can be resolved by Sema.
- if ((*SS)->isValid()) { // Resolved qualifier.
- return {GetAllAccessibleScopes(CCContext).scopesForIndexQuery(), false};
- }
-
- // Unresolved qualifier.
- // FIXME: When Sema can resolve part of a scope chain (e.g.
- // "known::unknown::id"), we should expand the known part ("known::") rather
- // than treating the whole thing as unknown.
- SpecifiedScope Info;
- Info.AccessibleScopes.push_back(""); // global namespace
-
- Info.UnresolvedQualifier =
- Lexer::getSourceText(CharSourceRange::getCharRange((*SS)->getRange()),
- CCSema.SourceMgr, clang::LangOptions())
- .ltrim("::");
+ // Case 3: sema saw and resolved a scope qualifier.
+ if (SemaSpecifier && SemaSpecifier->isValid())
+ return {Scopes.scopesForIndexQuery(), false};
+
+ // Case 4: There was a qualifier, and Sema didn't resolve it.
+ Scopes.AccessibleScopes.push_back(""); // Make sure global scope is included.
+ llvm::StringRef SpelledSpecifier = Lexer::getSourceText(
+ CharSourceRange::getCharRange(SemaSpecifier->getRange()),
+ CCSema.SourceMgr, clang::LangOptions());
+ if (SpelledSpecifier.consume_front("::"))
+ Scopes.AccessibleScopes = {""};
+ Scopes.UnresolvedQualifier = SpelledSpecifier;
// Sema excludes the trailing "::".
- if (!Info.UnresolvedQualifier->empty())
- *Info.UnresolvedQualifier += "::";
+ if (!Scopes.UnresolvedQualifier->empty())
+ *Scopes.UnresolvedQualifier += "::";
- return {Info.scopesForIndexQuery(), false};
+ return {Scopes.scopesForIndexQuery(), false};
}
// Should we perform index-based completion in a context of the specified kind?
@@ -691,8 +705,7 @@ static bool isBlacklistedMember(const NamedDecl &D) {
struct CompletionRecorder : public CodeCompleteConsumer {
CompletionRecorder(const CodeCompleteOptions &Opts,
llvm::unique_function<void()> ResultsCallback)
- : CodeCompleteConsumer(Opts.getClangCompleteOpts(),
- /*OutputIsBinary=*/false),
+ : CodeCompleteConsumer(Opts.getClangCompleteOpts()),
CCContext(CodeCompletionContext::CCC_Other), Opts(Opts),
CCAllocator(std::make_shared<GlobalCodeCompletionAllocator>()),
CCTUInfo(CCAllocator), ResultsCallback(std::move(ResultsCallback)) {
@@ -813,8 +826,7 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
public:
SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
const SymbolIndex *Index, SignatureHelp &SigHelp)
- : CodeCompleteConsumer(CodeCompleteOpts,
- /*OutputIsBinary=*/false),
+ : CodeCompleteConsumer(CodeCompleteOpts),
SigHelp(SigHelp),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator), Index(Index) {}
@@ -1005,11 +1017,28 @@ struct SemaCompleteInput {
const tooling::CompileCommand &Command;
const PreambleData *Preamble;
llvm::StringRef Contents;
- Position Pos;
+ size_t Offset;
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS;
- std::shared_ptr<PCHContainerOperations> PCHs;
};
+void loadMainFilePreambleMacros(const Preprocessor &PP,
+ const PreambleData &Preamble) {
+ // The ExternalPreprocessorSource has our macros, if we know where to look.
+ // We can read all the macros using PreambleMacros->ReadDefinedMacros(),
+ // but this includes transitively included files, so may deserialize a lot.
+ ExternalPreprocessorSource *PreambleMacros = PP.getExternalSource();
+ // As we have the names of the macros, we can look up their IdentifierInfo
+ // and then use this to load just the macros we want.
+ IdentifierInfoLookup *PreambleIdentifiers =
+ PP.getIdentifierTable().getExternalIdentifierLookup();
+ if (!PreambleIdentifiers || !PreambleMacros)
+ return;
+ for (const auto& MacroName : Preamble.MainFileMacros)
+ if (auto *II = PreambleIdentifiers->get(MacroName))
+ if (II->isOutOfDate())
+ PreambleMacros->updateOutOfDateIdentifier(*II);
+}
+
// Invokes Sema code completion on a file.
// If \p Includes is set, it will be updated based on the compiler invocation.
bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
@@ -1017,46 +1046,29 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
const SemaCompleteInput &Input,
IncludeStructure *Includes = nullptr) {
trace::Span Tracer("Sema completion");
- std::vector<const char *> ArgStrs;
- for (const auto &S : Input.Command.CommandLine)
- ArgStrs.push_back(S.c_str());
-
- if (Input.VFS->setCurrentWorkingDirectory(Input.Command.Directory)) {
- log("Couldn't set working directory");
- // We run parsing anyway, our lit-tests rely on results for non-existing
- // working dirs.
- }
-
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = Input.VFS;
if (Input.Preamble && Input.Preamble->StatCache)
VFS = Input.Preamble->StatCache->getConsumingFS(std::move(VFS));
- IgnoreDiagnostics DummyDiagsConsumer;
- auto CI = createInvocationFromCommandLine(
- ArgStrs,
- CompilerInstance::createDiagnostics(new DiagnosticOptions,
- &DummyDiagsConsumer, false),
- VFS);
+ ParseInputs ParseInput;
+ ParseInput.CompileCommand = Input.Command;
+ ParseInput.FS = VFS;
+ ParseInput.Contents = Input.Contents;
+ ParseInput.Opts = ParseOptions();
+ auto CI = buildCompilerInvocation(ParseInput);
if (!CI) {
elog("Couldn't create CompilerInvocation");
return false;
}
auto &FrontendOpts = CI->getFrontendOpts();
- FrontendOpts.DisableFree = false;
FrontendOpts.SkipFunctionBodies = true;
- CI->getLangOpts()->CommentOpts.ParseAllComments = true;
// Disable typo correction in Sema.
CI->getLangOpts()->SpellChecking = false;
// Setup code completion.
FrontendOpts.CodeCompleteOpts = Options;
FrontendOpts.CodeCompletionAt.FileName = Input.FileName;
- auto Offset = positionToOffset(Input.Contents, Input.Pos);
- if (!Offset) {
- elog("Code completion position was invalid {0}", Offset.takeError());
- return false;
- }
std::tie(FrontendOpts.CodeCompletionAt.Line,
FrontendOpts.CodeCompletionAt.Column) =
- offsetToClangLineColumn(Input.Contents, *Offset);
+ offsetToClangLineColumn(Input.Contents, Input.Offset);
std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
llvm::MemoryBuffer::getMemBufferCopy(Input.Contents, Input.FileName);
@@ -1068,17 +1080,17 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
// However, if we're completing *inside* the preamble section of the draft,
// overriding the preamble will break sema completion. Fortunately we can just
// skip all includes in this case; these completions are really simple.
- bool CompletingInPreamble =
- ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0).Size >
- *Offset;
+ PreambleBounds PreambleRegion =
+ ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0);
+ bool CompletingInPreamble = PreambleRegion.Size > Input.Offset;
// NOTE: we must call BeginSourceFile after prepareCompilerInstance. Otherwise
// the remapped buffers do not get freed.
+ IgnoreDiagnostics DummyDiagsConsumer;
auto Clang = prepareCompilerInstance(
std::move(CI),
(Input.Preamble && !CompletingInPreamble) ? &Input.Preamble->Preamble
: nullptr,
- std::move(ContentsBuffer), std::move(Input.PCHs), std::move(VFS),
- DummyDiagsConsumer);
+ std::move(ContentsBuffer), std::move(VFS), DummyDiagsConsumer);
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());
@@ -1088,6 +1100,14 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
Input.FileName);
return false;
}
+ // Macros can be defined within the preamble region of the main file.
+ // They don't fall nicely into our index/Sema dichotomy:
+ // - they're not indexed for completion (they're not available across files)
+ // - but Sema code complete won't see them: as part of the preamble, they're
+ // deserialized only when mentioned.
+ // Force them to be deserialized so SemaCodeComplete sees them.
+ if (Input.Preamble)
+ loadMainFilePreambleMacros(Clang->getPreprocessor(), *Input.Preamble);
if (Includes)
Clang->getPreprocessor().addPPCallbacks(
collectIncludeStructureCallback(Clang->getSourceManager(), Includes));
@@ -1141,39 +1161,12 @@ std::future<SymbolSlab> startAsyncFuzzyFind(const SymbolIndex &Index,
// Creates a `FuzzyFindRequest` based on the cached index request from the
// last completion, if any, and the speculated completion filter text in the
// source code.
-llvm::Optional<FuzzyFindRequest>
-speculativeFuzzyFindRequestForCompletion(FuzzyFindRequest CachedReq,
- PathRef File, llvm::StringRef Content,
- Position Pos) {
- auto Filter = speculateCompletionFilter(Content, Pos);
- if (!Filter) {
- elog("Failed to speculate filter text for code completion at Pos "
- "{0}:{1}: {2}",
- Pos.line, Pos.character, Filter.takeError());
- return None;
- }
- CachedReq.Query = *Filter;
+FuzzyFindRequest speculativeFuzzyFindRequestForCompletion(
+ FuzzyFindRequest CachedReq, const CompletionPrefix &HeuristicPrefix) {
+ CachedReq.Query = HeuristicPrefix.Name;
return CachedReq;
}
-// Returns the most popular include header for \p Sym. If two headers are
-// equally popular, prefer the shorter one. Returns empty string if \p Sym has
-// no include header.
-llvm::SmallVector<llvm::StringRef, 1> getRankedIncludes(const Symbol &Sym) {
- auto Includes = Sym.IncludeHeaders;
- // Sort in descending order by reference count and header length.
- llvm::sort(Includes, [](const Symbol::IncludeHeaderWithReferences &LHS,
- const Symbol::IncludeHeaderWithReferences &RHS) {
- if (LHS.References == RHS.References)
- return LHS.IncludeHeader.size() < RHS.IncludeHeader.size();
- return LHS.References > RHS.References;
- });
- llvm::SmallVector<llvm::StringRef, 1> Headers;
- for (const auto &Include : Includes)
- Headers.push_back(Include.IncludeHeader);
- return Headers;
-}
-
// Runs Sema-based (AST) and Index-based completion, returns merged results.
//
// There are a few tricky considerations:
@@ -1211,15 +1204,20 @@ class CodeCompleteFlow {
// Sema takes ownership of Recorder. Recorder is valid until Sema cleanup.
CompletionRecorder *Recorder = nullptr;
- int NSema = 0, NIndex = 0, NBoth = 0; // Counters for logging.
- bool Incomplete = false; // Would more be available with a higher limit?
+ CodeCompletionContext::Kind CCContextKind = CodeCompletionContext::CCC_Other;
+ // Counters for logging.
+ int NSema = 0, NIndex = 0, NSemaAndIndex = 0, NIdent = 0;
+ bool Incomplete = false; // Would more be available with a higher limit?
+ CompletionPrefix HeuristicPrefix;
llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
+ Range ReplacedRange;
std::vector<std::string> QueryScopes; // Initialized once Sema runs.
// Initialized once QueryScopes is initialized, if there are scopes.
llvm::Optional<ScopeDistance> ScopeProximity;
llvm::Optional<OpaqueType> PreferredType; // Initialized once Sema runs.
// Whether to query symbols from any scope. Initialized once Sema runs.
bool AllScopes = false;
+ llvm::StringSet<> ContextWords;
// Include-insertion and proximity scoring rely on the include structure.
// This is available after Sema has run.
llvm::Optional<IncludeInserter> Inserter; // Available during runWithSema.
@@ -1240,12 +1238,14 @@ public:
CodeCompleteResult run(const SemaCompleteInput &SemaCCInput) && {
trace::Span Tracer("CodeCompleteFlow");
+ HeuristicPrefix =
+ guessCompletionPrefix(SemaCCInput.Contents, SemaCCInput.Offset);
+ populateContextWords(SemaCCInput.Contents);
if (Opts.Index && SpecFuzzyFind && SpecFuzzyFind->CachedReq.hasValue()) {
assert(!SpecFuzzyFind->Result.valid());
- if ((SpecReq = speculativeFuzzyFindRequestForCompletion(
- *SpecFuzzyFind->CachedReq, SemaCCInput.FileName,
- SemaCCInput.Contents, SemaCCInput.Pos)))
- SpecFuzzyFind->Result = startAsyncFuzzyFind(*Opts.Index, *SpecReq);
+ SpecReq = speculativeFuzzyFindRequestForCompletion(
+ *SpecFuzzyFind->CachedReq, HeuristicPrefix);
+ SpecFuzzyFind->Result = startAsyncFuzzyFind(*Opts.Index, *SpecReq);
}
// We run Sema code completion first. It builds an AST and calculates:
@@ -1254,21 +1254,15 @@ public:
CodeCompleteResult Output;
auto RecorderOwner = llvm::make_unique<CompletionRecorder>(Opts, [&]() {
assert(Recorder && "Recorder is not set");
- auto Style =
- format::getStyle(format::DefaultFormatStyle, SemaCCInput.FileName,
- format::DefaultFallbackStyle, SemaCCInput.Contents,
- SemaCCInput.VFS.get());
- if (!Style) {
- log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.",
- SemaCCInput.FileName, Style.takeError());
- Style = format::getLLVMStyle();
- }
+ CCContextKind = Recorder->CCContext.getKind();
+ auto Style = getFormatStyleForFile(
+ SemaCCInput.FileName, SemaCCInput.Contents, SemaCCInput.VFS.get());
// If preprocessor was run, inclusions from preprocessor callback should
// already be added to Includes.
Inserter.emplace(
- SemaCCInput.FileName, SemaCCInput.Contents, *Style,
+ SemaCCInput.FileName, SemaCCInput.Contents, Style,
SemaCCInput.Command.Directory,
- Recorder->CCSema->getPreprocessor().getHeaderSearchInfo());
+ &Recorder->CCSema->getPreprocessor().getHeaderSearchInfo());
for (const auto &Inc : Includes.MainFileIncludes)
Inserter->addExisting(Inc);
@@ -1294,10 +1288,10 @@ public:
Output = runWithSema();
Inserter.reset(); // Make sure this doesn't out-live Clang.
SPAN_ATTACH(Tracer, "sema_completion_kind",
- getCompletionKindString(Recorder->CCContext.getKind()));
+ getCompletionKindString(CCContextKind));
log("Code complete: sema context {0}, query scopes [{1}] (AnyScope={2}), "
"expected type {3}",
- getCompletionKindString(Recorder->CCContext.getKind()),
+ getCompletionKindString(CCContextKind),
llvm::join(QueryScopes.begin(), QueryScopes.end(), ","), AllScopes,
PreferredType ? Recorder->CCContext.getPreferredType().getAsString()
: "<none>");
@@ -1307,47 +1301,132 @@ public:
semaCodeComplete(std::move(RecorderOwner), Opts.getClangCompleteOpts(),
SemaCCInput, &Includes);
+ logResults(Output, Tracer);
+ return Output;
+ }
+ void logResults(const CodeCompleteResult &Output, const trace::Span &Tracer) {
SPAN_ATTACH(Tracer, "sema_results", NSema);
SPAN_ATTACH(Tracer, "index_results", NIndex);
- SPAN_ATTACH(Tracer, "merged_results", NBoth);
+ SPAN_ATTACH(Tracer, "merged_results", NSemaAndIndex);
+ SPAN_ATTACH(Tracer, "identifier_results", NIdent);
SPAN_ATTACH(Tracer, "returned_results", int64_t(Output.Completions.size()));
SPAN_ATTACH(Tracer, "incomplete", Output.HasMore);
log("Code complete: {0} results from Sema, {1} from Index, "
- "{2} matched, {3} returned{4}.",
- NSema, NIndex, NBoth, Output.Completions.size(),
+ "{2} matched, {3} from identifiers, {4} returned{5}.",
+ NSema, NIndex, NSemaAndIndex, NIdent, Output.Completions.size(),
Output.HasMore ? " (incomplete)" : "");
assert(!Opts.Limit || Output.Completions.size() <= Opts.Limit);
// We don't assert that isIncomplete means we hit a limit.
// Indexes may choose to impose their own limits even if we don't have one.
+ }
+
+ CodeCompleteResult
+ runWithoutSema(llvm::StringRef Content, size_t Offset,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) && {
+ trace::Span Tracer("CodeCompleteWithoutSema");
+ // Fill in fields normally set by runWithSema()
+ HeuristicPrefix = guessCompletionPrefix(Content, Offset);
+ populateContextWords(Content);
+ CCContextKind = CodeCompletionContext::CCC_Recovery;
+ Filter = FuzzyMatcher(HeuristicPrefix.Name);
+ auto Pos = offsetToPosition(Content, Offset);
+ ReplacedRange.start = ReplacedRange.end = Pos;
+ ReplacedRange.start.character -= HeuristicPrefix.Name.size();
+
+ llvm::StringMap<SourceParams> ProxSources;
+ ProxSources[FileName].Cost = 0;
+ FileProximity.emplace(ProxSources);
+
+ auto Style = getFormatStyleForFile(FileName, Content, VFS.get());
+ // This will only insert verbatim headers.
+ Inserter.emplace(FileName, Content, Style,
+ /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
+
+ auto Identifiers = collectIdentifiers(Content, Style);
+ std::vector<RawIdentifier> IdentifierResults;
+ for (const auto &IDAndCount : Identifiers) {
+ RawIdentifier ID;
+ ID.Name = IDAndCount.first();
+ ID.References = IDAndCount.second;
+ // Avoid treating typed filter as an identifier.
+ if (ID.Name == HeuristicPrefix.Name)
+ --ID.References;
+ if (ID.References > 0)
+ IdentifierResults.push_back(std::move(ID));
+ }
+
+ // Simplified version of getQueryScopes():
+ // - accessible scopes are determined heuristically.
+ // - all-scopes query if no qualifier was typed (and it's allowed).
+ SpecifiedScope Scopes;
+ Scopes.AccessibleScopes =
+ visibleNamespaces(Content.take_front(Offset), Style);
+ for (std::string &S : Scopes.AccessibleScopes)
+ if (!S.empty())
+ S.append("::"); // visibleNamespaces doesn't include trailing ::.
+ if (HeuristicPrefix.Qualifier.empty())
+ AllScopes = Opts.AllScopes;
+ else if (HeuristicPrefix.Qualifier.startswith("::")) {
+ Scopes.AccessibleScopes = {""};
+ Scopes.UnresolvedQualifier = HeuristicPrefix.Qualifier.drop_front(2);
+ } else
+ Scopes.UnresolvedQualifier = HeuristicPrefix.Qualifier;
+ // First scope is the (modified) enclosing scope.
+ QueryScopes = Scopes.scopesForIndexQuery();
+ ScopeProximity.emplace(QueryScopes);
+
+ SymbolSlab IndexResults = Opts.Index ? queryIndex() : SymbolSlab();
+
+ CodeCompleteResult Output = toCodeCompleteResult(mergeResults(
+ /*SemaResults=*/{}, IndexResults, IdentifierResults));
+ Output.RanParser = false;
+ logResults(Output, Tracer);
return Output;
}
private:
+ void populateContextWords(llvm::StringRef Content) {
+ // Take last 3 lines before the completion point.
+ unsigned RangeEnd = HeuristicPrefix.Qualifier.begin() - Content.data(),
+ RangeBegin = RangeEnd;
+ for (size_t I = 0; I < 3 && RangeBegin > 0; ++I) {
+ auto PrevNL = Content.rfind('\n', RangeBegin - 1);
+ if (PrevNL == StringRef::npos) {
+ RangeBegin = 0;
+ break;
+ }
+ RangeBegin = PrevNL + 1;
+ }
+
+ ContextWords = collectWords(Content.slice(RangeBegin, RangeEnd));
+ dlog("Completion context words: {0}",
+ llvm::join(ContextWords.keys(), ", "));
+ }
+
// This is called by run() once Sema code completion is done, but before the
// Sema data structures are torn down. It does all the real work.
CodeCompleteResult runWithSema() {
const auto &CodeCompletionRange = CharSourceRange::getCharRange(
Recorder->CCSema->getPreprocessor().getCodeCompletionTokenRange());
- Range TextEditRange;
// When we are getting completions with an empty identifier, for example
// std::vector<int> asdf;
// asdf.^;
// Then the range will be invalid and we will be doing insertion, use
// current cursor position in such cases as range.
if (CodeCompletionRange.isValid()) {
- TextEditRange = halfOpenToRange(Recorder->CCSema->getSourceManager(),
+ ReplacedRange = halfOpenToRange(Recorder->CCSema->getSourceManager(),
CodeCompletionRange);
} else {
const auto &Pos = sourceLocToPosition(
Recorder->CCSema->getSourceManager(),
Recorder->CCSema->getPreprocessor().getCodeCompletionLoc());
- TextEditRange.start = TextEditRange.end = Pos;
+ ReplacedRange.start = ReplacedRange.end = Pos;
}
Filter = FuzzyMatcher(
Recorder->CCSema->getPreprocessor().getCodeCompletionFilter());
- std::tie(QueryScopes, AllScopes) =
- getQueryScopes(Recorder->CCContext, *Recorder->CCSema, Opts);
+ std::tie(QueryScopes, AllScopes) = getQueryScopes(
+ Recorder->CCContext, *Recorder->CCSema, HeuristicPrefix, Opts);
if (!QueryScopes.empty())
ScopeProximity.emplace(QueryScopes);
PreferredType =
@@ -1363,18 +1442,23 @@ private:
: SymbolSlab();
trace::Span Tracer("Populate CodeCompleteResult");
// Merge Sema and Index results, score them, and pick the winners.
- auto Top = mergeResults(Recorder->Results, IndexResults);
+ auto Top =
+ mergeResults(Recorder->Results, IndexResults, /*Identifiers*/ {});
+ return toCodeCompleteResult(Top);
+ }
+
+ CodeCompleteResult
+ toCodeCompleteResult(const std::vector<ScoredBundle> &Scored) {
CodeCompleteResult Output;
// Convert the results to final form, assembling the expensive strings.
- for (auto &C : Top) {
+ for (auto &C : Scored) {
Output.Completions.push_back(toCodeCompletion(C.first));
Output.Completions.back().Score = C.second;
- Output.Completions.back().CompletionTokenRange = TextEditRange;
+ Output.Completions.back().CompletionTokenRange = ReplacedRange;
}
Output.HasMore = Incomplete;
- Output.Context = Recorder->CCContext.getKind();
-
+ Output.Context = CCContextKind;
return Output;
}
@@ -1392,6 +1476,8 @@ private:
Req.AnyScope = AllScopes;
// FIXME: we should send multiple weighted paths here.
Req.ProximityPaths.push_back(FileName);
+ if (PreferredType)
+ Req.PreferredTypes.push_back(PreferredType->raw());
vlog("Code complete: fuzzyFind({0:2})", toJSON(Req));
if (SpecFuzzyFind)
@@ -1416,23 +1502,34 @@ private:
}
// Merges Sema and Index results where possible, to form CompletionCandidates.
+ // \p Identifiers is raw idenfiers that can also be completion condidates.
+ // Identifiers are not merged with results from index or sema.
// Groups overloads if desired, to form CompletionCandidate::Bundles. The
// bundles are scored and top results are returned, best to worst.
std::vector<ScoredBundle>
mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
- const SymbolSlab &IndexResults) {
+ const SymbolSlab &IndexResults,
+ const std::vector<RawIdentifier> &IdentifierResults) {
trace::Span Tracer("Merge and score results");
std::vector<CompletionCandidate::Bundle> Bundles;
llvm::DenseMap<size_t, size_t> BundleLookup;
auto AddToBundles = [&](const CodeCompletionResult *SemaResult,
- const Symbol *IndexResult) {
+ const Symbol *IndexResult,
+ const RawIdentifier *IdentifierResult) {
CompletionCandidate C;
C.SemaResult = SemaResult;
C.IndexResult = IndexResult;
- if (C.IndexResult)
+ C.IdentifierResult = IdentifierResult;
+ if (C.IndexResult) {
+ C.Name = IndexResult->Name;
C.RankedIncludeHeaders = getRankedIncludes(*C.IndexResult);
- C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult);
- if (auto OverloadSet = Opts.BundleOverloads ? C.overloadSet() : 0) {
+ } else if (C.SemaResult) {
+ C.Name = Recorder->getName(*SemaResult);
+ } else {
+ assert(IdentifierResult);
+ C.Name = IdentifierResult->Name;
+ }
+ if (auto OverloadSet = C.overloadSet(Opts)) {
auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
if (Ret.second)
Bundles.emplace_back();
@@ -1456,14 +1553,17 @@ private:
return nullptr;
};
// Emit all Sema results, merging them with Index results if possible.
- for (auto &SemaResult : Recorder->Results)
- AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult));
+ for (auto &SemaResult : SemaResults)
+ AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult), nullptr);
// Now emit any Index-only results.
for (const auto &IndexResult : IndexResults) {
if (UsedIndexResults.count(&IndexResult))
continue;
- AddToBundles(/*SemaResult=*/nullptr, &IndexResult);
+ AddToBundles(/*SemaResult=*/nullptr, &IndexResult, nullptr);
}
+ // Emit identifier results.
+ for (const auto &Ident : IdentifierResults)
+ AddToBundles(/*SemaResult=*/nullptr, /*IndexResult=*/nullptr, &Ident);
// We only keep the best N results at any time, in "native" format.
TopN<ScoredBundle, ScoredBundleGreater> Top(
Opts.Limit == 0 ? std::numeric_limits<size_t>::max() : Opts.Limit);
@@ -1486,13 +1586,15 @@ private:
CompletionCandidate::Bundle Bundle) {
SymbolQualitySignals Quality;
SymbolRelevanceSignals Relevance;
- Relevance.Context = Recorder->CCContext.getKind();
+ Relevance.Context = CCContextKind;
+ Relevance.Name = Bundle.front().Name;
Relevance.Query = SymbolRelevanceSignals::CodeComplete;
Relevance.FileProximityMatch = FileProximity.getPointer();
if (ScopeProximity)
Relevance.ScopeProximityMatch = ScopeProximity.getPointer();
if (PreferredType)
Relevance.HadContextType = true;
+ Relevance.ContextWords = &ContextWords;
auto &First = Bundle.front();
if (auto FuzzyScore = fuzzyScore(First))
@@ -1527,6 +1629,11 @@ private:
}
Origin |= SymbolOrigin::AST;
}
+ if (Candidate.IdentifierResult) {
+ Quality.References = Candidate.IdentifierResult->References;
+ Relevance.Scope = SymbolRelevanceSignals::FileScope;
+ Origin |= SymbolOrigin::Identifier;
+ }
}
CodeCompletion::Scores Scores;
@@ -1544,7 +1651,8 @@ private:
NSema += bool(Origin & SymbolOrigin::AST);
NIndex += FromIndex;
- NBoth += bool(Origin & SymbolOrigin::AST) && FromIndex;
+ NSemaAndIndex += bool(Origin & SymbolOrigin::AST) && FromIndex;
+ NIdent += bool(Origin & SymbolOrigin::Identifier);
if (Candidates.push({std::move(Bundle), Scores}))
Incomplete = true;
}
@@ -1556,9 +1664,9 @@ private:
Item.SemaResult ? Recorder->codeCompletionString(*Item.SemaResult)
: nullptr;
if (!Builder)
- Builder.emplace(Recorder->CCSema->getASTContext(), Item, SemaCCS,
- QueryScopes, *Inserter, FileName,
- Recorder->CCContext.getKind(), Opts);
+ Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr,
+ Item, SemaCCS, QueryScopes, *Inserter, FileName,
+ CCContextKind, Opts);
else
Builder->add(Item, SemaCCS);
}
@@ -1566,6 +1674,13 @@ private:
}
};
+template <class T> bool isExplicitTemplateSpecialization(const NamedDecl &ND) {
+ if (const auto *TD = dyn_cast<T>(&ND))
+ if (TD->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
+ return true;
+ return false;
+}
+
} // namespace
clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
@@ -1588,41 +1703,44 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
return Result;
}
-llvm::Expected<llvm::StringRef>
-speculateCompletionFilter(llvm::StringRef Content, Position Pos) {
- auto Offset = positionToOffset(Content, Pos);
- if (!Offset)
- return llvm::make_error<llvm::StringError>(
- "Failed to convert position to offset in content.",
- llvm::inconvertibleErrorCode());
- if (*Offset == 0)
- return "";
+CompletionPrefix
+guessCompletionPrefix(llvm::StringRef Content, unsigned Offset) {
+ assert(Offset <= Content.size());
+ StringRef Rest = Content.take_front(Offset);
+ CompletionPrefix Result;
+
+ // Consume the unqualified name. We only handle ASCII characters.
+ // isIdentifierBody will let us match "0invalid", but we don't mind.
+ while (!Rest.empty() && isIdentifierBody(Rest.back()))
+ Rest = Rest.drop_back();
+ Result.Name = Content.slice(Rest.size(), Offset);
+
+ // Consume qualifiers.
+ while (Rest.consume_back("::") && !Rest.endswith(":")) // reject ::::
+ while (!Rest.empty() && isIdentifierBody(Rest.back()))
+ Rest = Rest.drop_back();
+ Result.Qualifier =
+ Content.slice(Rest.size(), Result.Name.begin() - Content.begin());
- // Start from the character before the cursor.
- int St = *Offset - 1;
- // FIXME(ioeric): consider UTF characters?
- auto IsValidIdentifierChar = [](char c) {
- return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
- (c >= '0' && c <= '9') || (c == '_'));
- };
- size_t Len = 0;
- for (; (St >= 0) && IsValidIdentifierChar(Content[St]); --St, ++Len) {
- }
- if (Len > 0)
- St++; // Shift to the first valid character.
- return Content.substr(St, Len);
+ return Result;
}
CodeCompleteResult
codeComplete(PathRef FileName, const tooling::CompileCommand &Command,
const PreambleData *Preamble, llvm::StringRef Contents,
Position Pos, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
- std::shared_ptr<PCHContainerOperations> PCHs,
CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind) {
- return CodeCompleteFlow(FileName,
- Preamble ? Preamble->Includes : IncludeStructure(),
- SpecFuzzyFind, Opts)
- .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs});
+ auto Offset = positionToOffset(Contents, Pos);
+ if (!Offset) {
+ elog("Code completion position was invalid {0}", Offset.takeError());
+ return CodeCompleteResult();
+ }
+ auto Flow = CodeCompleteFlow(
+ FileName, Preamble ? Preamble->Includes : IncludeStructure(),
+ SpecFuzzyFind, Opts);
+ return Preamble ? std::move(Flow).run(
+ {FileName, Command, Preamble, Contents, *Offset, VFS})
+ : std::move(Flow).runWithoutSema(Contents, *Offset, VFS);
}
SignatureHelp signatureHelp(PathRef FileName,
@@ -1630,8 +1748,12 @@ SignatureHelp signatureHelp(PathRef FileName,
const PreambleData *Preamble,
llvm::StringRef Contents, Position Pos,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
- std::shared_ptr<PCHContainerOperations> PCHs,
const SymbolIndex *Index) {
+ auto Offset = positionToOffset(Contents, Pos);
+ if (!Offset) {
+ elog("Code completion position was invalid {0}", Offset.takeError());
+ return SignatureHelp();
+ }
SignatureHelp Result;
clang::CodeCompleteOptions Options;
Options.IncludeGlobals = false;
@@ -1642,8 +1764,7 @@ SignatureHelp signatureHelp(PathRef FileName,
semaCodeComplete(
llvm::make_unique<SignatureHelpCollector>(Options, Index, Result),
Options,
- {FileName, Command, Preamble, Contents, Pos, std::move(VFS),
- std::move(PCHs)});
+ {FileName, Command, Preamble, Contents, *Offset, std::move(VFS)});
return Result;
}
@@ -1659,6 +1780,13 @@ bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) {
};
return false;
};
+ // We only complete symbol's name, which is the same as the name of the
+ // *primary* template in case of template specializations.
+ if (isExplicitTemplateSpecialization<FunctionDecl>(ND) ||
+ isExplicitTemplateSpecialization<CXXRecordDecl>(ND) ||
+ isExplicitTemplateSpecialization<VarDecl>(ND))
+ return false;
+
if (InTopLevelScope(ND))
return true;
@@ -1695,7 +1823,7 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
// is mainly to help LSP clients again, so that changes do not effect each
// other.
for (const auto &FixIt : FixIts) {
- if (IsRangeConsecutive(FixIt.range, LSP.textEdit->range)) {
+ if (isRangeConsecutive(FixIt.range, LSP.textEdit->range)) {
LSP.textEdit->newText = FixIt.newText + LSP.textEdit->newText;
LSP.textEdit->range.start = FixIt.range.start;
} else {