diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -158,6 +158,19 @@ unsigned References; // # of usages in file. }; +struct PostfixCompletionResult { + // Display string, not inserted. + std::string Signature; + // The range being replaced by ReplacedText. + // e.g. + // is_success.if + // ~~~~~~~~~~~~~ + Range ReplacedRange; + // The snippet being inserted when doing the completion. + // e.g. if (is_success) {} + std::string SnippetText; +}; + /// A code completion result, in clang-native form. /// It may be promoted to a CompletionItem if it's among the top-ranked results. struct CompletionCandidate { @@ -166,6 +179,7 @@ const CodeCompletionResult *SemaResult = nullptr; const Symbol *IndexResult = nullptr; const RawIdentifier *IdentifierResult = nullptr; + const PostfixCompletionResult *PostfixResult = nullptr; llvm::SmallVector RankedIncludeHeaders; // Returns a token identifying the overload set this is part of. @@ -206,7 +220,7 @@ return llvm::hash_combine(Scratch, headerToInsertIfAllowed(Opts).getValueOr("")); } - assert(IdentifierResult); + assert(IdentifierResult || PostfixResult); return 0; } @@ -316,6 +330,11 @@ Completion.Kind = CompletionItemKind::Text; Completion.Name = std::string(C.IdentifierResult->Name); } + if (C.PostfixResult) { + Completion.Signature = C.PostfixResult->Signature; + Completion.Kind = CompletionItemKind::Snippet; + Completion.SnippetSuffix = C.PostfixResult->SnippetText; + } // Turn absolute path into a literal string that can be #included. auto Inserted = [&](llvm::StringRef Header) @@ -371,6 +390,9 @@ S.Signature = std::string(C.IndexResult->Signature); S.SnippetSuffix = std::string(C.IndexResult->CompletionSnippetSuffix); S.ReturnType = std::string(C.IndexResult->ReturnType); + } else if (C.PostfixResult) { + S.SnippetSuffix = C.PostfixResult->SnippetText; + S.Signature = C.PostfixResult->Signature; } if (ExtractDocumentation && !Completion.Documentation) { auto SetDoc = [&](llvm::StringRef Doc) { @@ -609,6 +631,15 @@ return {Scopes.scopesForIndexQuery(), false}; } +bool contextAllowsPostfix(enum CodeCompletionContext::Kind K) { + switch (K) { + case CodeCompletionContext::CCC_DotMemberAccess: + return true; + default: + return false; + } +} + // Should we perform index-based completion in a context of the specified kind? // FIXME: consider allowing completion, but restricting the result types. bool contextAllowsIndex(enum CodeCompletionContext::Kind K) { @@ -721,8 +752,10 @@ // If a callback is called without any sema result and the context does not // support index-based completion, we simply skip it to give way to // potential future callbacks with results. - if (NumResults == 0 && !contextAllowsIndex(Context.getKind())) + if (NumResults == 0 && !contextAllowsIndex(Context.getKind()) && + !contextAllowsPostfix(Context.getKind())) { return; + } if (CCSema) { log("Multiple code complete callbacks (parser backtracked?). " "Dropping results from context {0}, keeping results from {1}.", @@ -1405,7 +1438,7 @@ SymbolSlab IndexResults = Opts.Index ? queryIndex() : SymbolSlab(); CodeCompleteResult Output = toCodeCompleteResult(mergeResults( - /*SemaResults=*/{}, IndexResults, IdentifierResults)); + /*SemaResults=*/{}, IndexResults, IdentifierResults, {})); Output.RanParser = false; logResults(Output, Tracer); return Output; @@ -1430,6 +1463,40 @@ llvm::join(ContextWords.keys(), ", ")); } + std::vector getPostfixResults() { + // Postfix completion requires snippet support. + if (!Opts.EnableSnippets) + return {}; + // Only trigger on ".". + if (Recorder->CCContext.getKind() != + CodeCompletionContext::CCC_DotMemberAccess) + return {}; + const auto &CCContext = Recorder->CCContext; + auto *CCSema = Recorder->CCSema; + const auto *BaseExpr = CCContext.getBaseExpr(); + if (!BaseExpr) + return {}; + + // Setup the CodeCompletionRange range. + // FIXME: ReplacedRange is a global variable used by all CompletionItems, + // refine it to support CompletionItems with variant completion ranges. + ReplacedRange = + halfOpenToRange(CCSema->SourceMgr, + CharSourceRange::getTokenRange( + BaseExpr->getBeginLoc(), + CCSema->getPreprocessor().getCodeCompletionLoc())); + llvm::StringRef SpelledExpr = Lexer::getSourceText( + CharSourceRange::getTokenRange(BaseExpr->getSourceRange()), + CCSema->SourceMgr, CCSema->getASTContext().getLangOpts()); + if (CCContext.getBaseType()->isBooleanType()) { + return {{/*Signature=*/"if (expressions) { statements }", ReplacedRange, + /*Snippet=*/ + llvm::formatv("if ({0}) { {1} }", SpelledExpr, "${0:statements}") + .str()}}; + } + return {}; + } + // 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() { @@ -1467,9 +1534,14 @@ ? queryIndex() : SymbolSlab(); trace::Span Tracer("Populate CodeCompleteResult"); + std::vector PostfixResults; + if (Recorder->Results.empty() && + contextAllowsPostfix(Recorder->CCContext.getKind())) { + PostfixResults = getPostfixResults(); + } // Merge Sema and Index results, score them, and pick the winners. - auto Top = - mergeResults(Recorder->Results, IndexResults, /*Identifiers*/ {}); + auto Top = mergeResults(Recorder->Results, IndexResults, /*Identifiers*/ {}, + std::move(PostfixResults)); return toCodeCompleteResult(Top); } @@ -1536,25 +1608,30 @@ std::vector mergeResults(const std::vector &SemaResults, const SymbolSlab &IndexResults, - const std::vector &IdentifierResults) { + const std::vector &IdentifierResults, + const std::vector &PostfixResults) { trace::Span Tracer("Merge and score results"); std::vector Bundles; llvm::DenseMap BundleLookup; auto AddToBundles = [&](const CodeCompletionResult *SemaResult, const Symbol *IndexResult, - const RawIdentifier *IdentifierResult) { + const RawIdentifier *IdentifierResult, + const PostfixCompletionResult *PostfixResult) { CompletionCandidate C; C.SemaResult = SemaResult; C.IndexResult = IndexResult; C.IdentifierResult = IdentifierResult; + C.PostfixResult = PostfixResult; if (C.IndexResult) { C.Name = IndexResult->Name; C.RankedIncludeHeaders = getRankedIncludes(*C.IndexResult); } else if (C.SemaResult) { C.Name = Recorder->getName(*SemaResult); - } else { + } else if (C.IdentifierResult) { assert(IdentifierResult); C.Name = IdentifierResult->Name; + } else { + assert(PostfixResult); } if (auto OverloadSet = C.overloadSet(Opts)) { auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size()); @@ -1581,16 +1658,21 @@ }; // Emit all Sema results, merging them with Index results if possible. for (auto &SemaResult : SemaResults) - AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult), nullptr); + AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult), nullptr, + nullptr); // Now emit any Index-only results. for (const auto &IndexResult : IndexResults) { if (UsedIndexResults.count(&IndexResult)) continue; - AddToBundles(/*SemaResult=*/nullptr, &IndexResult, nullptr); + AddToBundles(/*SemaResult=*/nullptr, &IndexResult, nullptr, nullptr); } // Emit identifier results. for (const auto &Ident : IdentifierResults) - AddToBundles(/*SemaResult=*/nullptr, /*IndexResult=*/nullptr, &Ident); + AddToBundles(/*SemaResult=*/nullptr, /*IndexResult=*/nullptr, &Ident, + nullptr); + for (const auto &PositFix : PostfixResults) + AddToBundles(/*SemaResult=*/nullptr, /*IndexResult=*/nullptr, nullptr, + &PositFix); // We only keep the best N results at any time, in "native" format. TopN Top( Opts.Limit == 0 ? std::numeric_limits::max() : Opts.Limit); @@ -1605,6 +1687,10 @@ if (C.SemaResult && C.SemaResult->Kind == CodeCompletionResult::RK_Macro && !C.Name.startswith_lower(Filter->pattern())) return None; + // Name in PostfixResult is empty, use the replaced text to do the match. + if (C.PostfixResult && llvm::StringRef(C.PostfixResult->SnippetText) + .startswith_lower(Filter->pattern())) + return Filter->match(C.PostfixResult->SnippetText); return Filter->match(C.Name); } @@ -1661,6 +1747,8 @@ Relevance.Scope = SymbolRelevanceSignals::FileScope; Origin |= SymbolOrigin::Identifier; } + if (Candidate.PostfixResult) + Origin |= SymbolOrigin::AST; } CodeCompletion::Scores Scores; diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -351,6 +351,8 @@ /// The type of the base object in a member access expression. QualType BaseType; + Expr *BaseExpr = nullptr; + /// The identifiers for Objective-C selector parts. ArrayRef SelIdents; @@ -394,6 +396,8 @@ /// Retrieve the type of the base object in a member-access /// expression. QualType getBaseType() const { return BaseType; } + void setBaseExpr(Expr *E) { BaseExpr = E; } + Expr *getBaseExpr() const { return BaseExpr; } /// Retrieve the Objective-C selector identifiers. ArrayRef getSelIdents() const { return SelIdents; } diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -5145,6 +5145,7 @@ } CodeCompletionContext CCContext(contextKind, ConvertedBaseType); + CCContext.setBaseExpr(Base); CCContext.setPreferredType(PreferredType); ResultBuilder Results(*this, CodeCompleter->getAllocator(), CodeCompleter->getCodeCompletionTUInfo(), CCContext,