hokein created this revision.
Herald added subscribers: cfe-commits, usaxena95, kadircet, arphaman, jkorous, 
MaskRay, ilya-biryukov.
Herald added a project: clang.
hokein edited the summary of this revision.

This is a quick proof-of-concept demo for postfix completion.

demo (ignore the wrong highlightings in VSCode):

F12057429: zII8TJLJQ5.gif <https://reviews.llvm.org/F12057429>

The big question: Where should we implement? in clangd, or in Sema?


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D80986

Files:
  clang-tools-extra/clangd/CodeComplete.cpp
  clang/include/clang/Sema/CodeCompleteConsumer.h
  clang/lib/Sema/SemaCodeComplete.cpp

Index: clang/lib/Sema/SemaCodeComplete.cpp
===================================================================
--- clang/lib/Sema/SemaCodeComplete.cpp
+++ 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,
Index: clang/include/clang/Sema/CodeCompleteConsumer.h
===================================================================
--- clang/include/clang/Sema/CodeCompleteConsumer.h
+++ 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<IdentifierInfo *> 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<IdentifierInfo *> getSelIdents() const { return SelIdents; }
Index: clang-tools-extra/clangd/CodeComplete.cpp
===================================================================
--- clang-tools-extra/clangd/CodeComplete.cpp
+++ 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<llvm::StringRef, 1> 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<PostfixCompletionResult> 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<PostfixCompletionResult> 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<ScoredBundle>
   mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
                const SymbolSlab &IndexResults,
-               const std::vector<RawIdentifier> &IdentifierResults) {
+               const std::vector<RawIdentifier> &IdentifierResults,
+               const std::vector<PostfixCompletionResult> &PostfixResults) {
     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 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<ScoredBundle, ScoredBundleGreater> Top(
         Opts.Limit == 0 ? std::numeric_limits<size_t>::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;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D80986: [clangd] Protot... Haojian Wu via Phabricator via cfe-commits

Reply via email to