sammccall updated this revision to Diff 524519.
sammccall edited the summary of this revision.
sammccall added a comment.

Add demo link


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D151166/new/

https://reviews.llvm.org/D151166

Files:
  clang-tools-extra/clangd/ASTMatchers.cpp
  clang-tools-extra/clangd/ASTMatchers.h
  clang-tools-extra/clangd/CMakeLists.txt
  clang-tools-extra/clangd/CodeComplete.cpp
  clang-tools-extra/clangd/Compiler.cpp
  clang-tools-extra/clangd/Diagnostics.cpp
  clang-tools-extra/clangd/ParsedAST.cpp
  clang-tools-extra/clangd/ParsedAST.h
  clang-tools-extra/clangd/Preamble.cpp
  clang-tools-extra/clangd/Preamble.h
  clang-tools-extra/clangd/XRefs.cpp
  clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
  clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
  clang-tools-extra/clangd/unittests/XRefsTests.cpp

Index: clang-tools-extra/clangd/unittests/XRefsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -2341,6 +2341,14 @@
   }
 }
 
+TEST(FindReferences, Matchers) {
+  checkFindRefs(R"cpp(
+    #pragma clang query \
+          ^parmVarDecl()           
+    void foo([[int x]], [[int y]]);
+  )cpp");
+}
+
 TEST(FindReferences, NeedsIndexForSymbols) {
   const char *Header = "int foo();";
   Annotations Main("int main() { [[f^oo]](); }");
Index: clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -361,6 +361,40 @@
                 diagSource(Diag::ClangTidy), diagName("llvm-include-order")))));
 }
 
+TEST(DiagnosticsTest, Matchers) {
+  Annotations Test(R"cpp(
+    ;
+    #pragma clang query parmVarDecl()
+    void foo([[int x]], [[int y]]);
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  EXPECT_THAT(*TU.build().getDiagnostics(),
+              ElementsAre(Diag(Test.ranges()[0], "query:3 matches"),
+                          Diag(Test.ranges()[1], "query:3 matches")));
+}
+
+TEST(DiagnosticsTest, MatchersPreamble) {
+  Annotations Test(R"cpp(
+    #pragma clang query parmVarDecl().bind("parm")
+    void foo([[int x]]);
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  EXPECT_THAT(*TU.build().getDiagnostics(),
+              ElementsAre(Diag(Test.range(), "parm matches")));
+}
+
+TEST(DiagnosticsTest, MatchersError) {
+  Annotations Test(R"cpp(
+    #pragma clang query [[pmVrDcl]]() // error-ok
+    void foo(int x);
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  EXPECT_THAT(
+      *TU.build().getDiagnostics(),
+      ElementsAre(
+          Diag(Test.range(), "Bad matcher: 1:2: Matcher not found: pmVrDcl")));
+}
+
 TEST(DiagnosticTest, TemplatesInHeaders) {
   // Diagnostics from templates defined in headers are placed at the expansion.
   Annotations Main(R"cpp(
Index: clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -3968,6 +3968,16 @@
   }
 }
 
+TEST(CompletionTest, MatcherPragma) {
+  Annotations Code(R"cpp(
+    #pragma clang query parmVarDecl(hasType^)
+  )cpp");
+  auto TU = TestTU::withCode(Code.code());
+
+  ASSERT_THAT(completions(TU, Code.point()).Completions,
+              ElementsAre(named("hasType"), named("hasTypeLoc")));
+}
+
 TEST(SignatureHelp, DocFormat) {
   Annotations Code(R"cpp(
     // Comment `with` markup.
Index: clang-tools-extra/clangd/XRefs.cpp
===================================================================
--- clang-tools-extra/clangd/XRefs.cpp
+++ clang-tools-extra/clangd/XRefs.cpp
@@ -67,6 +67,7 @@
 #include "llvm/Support/raw_ostream.h"
 #include <optional>
 #include <string>
+#include <utility>
 #include <vector>
 
 namespace clang {
@@ -1373,6 +1374,35 @@
     return std::nullopt;
   return Results;
 }
+
+std::optional<ReferencesResult>
+maybeFindMatcherReferences(ParsedAST &AST, Position Pos,
+                           URIForFile URIMainFile) {
+  const auto &Matchers = AST.getMatchers();
+  auto MatcherOnLine = llvm::find_if(Matchers, [&Pos](const MatcherPragma &M) {
+    return M.LineRange.first <= unsigned(Pos.line + 1) &&
+           unsigned(Pos.line + 1) <= M.LineRange.second;
+  });
+  if (MatcherOnLine == Matchers.end())
+    return std::nullopt;
+
+  ReferencesResult Results;
+  for (const auto &Match : MatcherOnLine->match(AST.getASTContext())) {
+    auto Range = AST.getTokens().spelledForExpanded(
+        AST.getTokens().expandedTokens(Match.second));
+    if (!Range || Range->empty())
+      continue;
+    ReferencesResult::Reference Ref;
+    Ref.Loc.uri = URIMainFile;
+    Ref.Loc.range.start =
+        sourceLocToPosition(AST.getSourceManager(), Range->front().location());
+    Ref.Loc.range.end = sourceLocToPosition(AST.getSourceManager(),
+                                            Range->back().endLocation());
+    Results.References.push_back(std::move(Ref));
+  }
+  return Results;
+}
+
 } // namespace
 
 ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
@@ -1387,10 +1417,12 @@
     return {};
   }
 
-  const auto IncludeReferences =
-      maybeFindIncludeReferences(AST, Pos, URIMainFile);
-  if (IncludeReferences)
+  if (auto IncludeReferences =
+          maybeFindIncludeReferences(AST, Pos, URIMainFile))
     return *IncludeReferences;
+  if (auto MatcherReferences =
+          maybeFindMatcherReferences(AST, Pos, URIMainFile))
+    return *MatcherReferences;
 
   llvm::DenseSet<SymbolID> IDsToQuery, OverriddenMethods;
 
Index: clang-tools-extra/clangd/Preamble.h
===================================================================
--- clang-tools-extra/clangd/Preamble.h
+++ clang-tools-extra/clangd/Preamble.h
@@ -22,6 +22,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PREAMBLE_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PREAMBLE_H
 
+#include "ASTMatchers.h"
 #include "CollectMacros.h"
 #include "Compiler.h"
 #include "Diagnostics.h"
@@ -71,6 +72,8 @@
   MainFileMacros Macros;
   // Pragma marks defined in the preamble section of the main file.
   std::vector<PragmaMark> Marks;
+  // Matcher pragmas defined in the preamble section of the main file.
+  std::vector<MatcherPragma> Matchers;
   // Cache of FS operations performed when building the preamble.
   // When reusing a preamble, this cache can be consumed to save IO.
   std::unique_ptr<PreambleFileStatusCache> StatCache;
Index: clang-tools-extra/clangd/Preamble.cpp
===================================================================
--- clang-tools-extra/clangd/Preamble.cpp
+++ clang-tools-extra/clangd/Preamble.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Preamble.h"
+#include "ASTMatchers.h"
 #include "CollectMacros.h"
 #include "Compiler.h"
 #include "Config.h"
@@ -89,6 +90,7 @@
   MainFileMacros takeMacros() { return std::move(Macros); }
 
   std::vector<PragmaMark> takeMarks() { return std::move(Marks); }
+  std::vector<MatcherPragma> takeMatchers() { return std::move(Matchers); }
 
   include_cleaner::PragmaIncludes takePragmaIncludes() {
     return std::move(Pragmas);
@@ -136,6 +138,7 @@
     PP = &CI.getPreprocessor();
     Includes.collect(CI);
     Pragmas.record(CI);
+    MatcherPragma::parse(&Matchers, CI);
     if (BeforeExecuteCallback)
       BeforeExecuteCallback(CI);
   }
@@ -208,6 +211,7 @@
   include_cleaner::PragmaIncludes Pragmas;
   MainFileMacros Macros;
   std::vector<PragmaMark> Marks;
+  std::vector<MatcherPragma> Matchers;
   bool IsMainFileIncludeGuarded = false;
   std::unique_ptr<CommentHandler> IWYUHandler = nullptr;
   const clang::LangOptions *LangOpts = nullptr;
@@ -679,6 +683,7 @@
     Result->Pragmas = CapturedInfo.takePragmaIncludes();
     Result->Macros = CapturedInfo.takeMacros();
     Result->Marks = CapturedInfo.takeMarks();
+    Result->Matchers = CapturedInfo.takeMatchers();
     Result->CanonIncludes = CapturedInfo.takeCanonicalIncludes();
     Result->StatCache = std::move(StatCache);
     Result->MainIsIncludeGuarded = CapturedInfo.isMainFileIncludeGuarded();
Index: clang-tools-extra/clangd/ParsedAST.h
===================================================================
--- clang-tools-extra/clangd/ParsedAST.h
+++ clang-tools-extra/clangd/ParsedAST.h
@@ -20,6 +20,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PARSEDAST_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PARSEDAST_H
 
+#include "ASTMatchers.h"
 #include "CollectMacros.h"
 #include "Compiler.h"
 #include "Diagnostics.h"
@@ -104,6 +105,8 @@
   const MainFileMacros &getMacros() const;
   /// Gets all pragma marks in the main file.
   const std::vector<PragmaMark> &getMarks() const;
+  /// Gets all AST matcher pragmas in the main file.
+  llvm::ArrayRef<MatcherPragma> getMatchers() const { return Matchers; }
   /// Tokens recorded while parsing the main file.
   /// (!) does not have tokens from the preamble.
   const syntax::TokenBuffer &getTokens() const { return Tokens; }
@@ -131,6 +134,7 @@
             std::unique_ptr<CompilerInstance> Clang,
             std::unique_ptr<FrontendAction> Action, syntax::TokenBuffer Tokens,
             MainFileMacros Macros, std::vector<PragmaMark> Marks,
+            std::vector<MatcherPragma> Matchers,
             std::vector<Decl *> LocalTopLevelDecls,
             std::optional<std::vector<Diag>> Diags, IncludeStructure Includes,
             CanonicalIncludes CanonIncludes);
@@ -157,6 +161,8 @@
   MainFileMacros Macros;
   // Pragma marks in the main file.
   std::vector<PragmaMark> Marks;
+  // #pragma clang query directives in the main file.
+  std::vector<MatcherPragma> Matchers;
   // Data, stored after parsing. std::nullopt if AST was built with a stale
   // preamble.
   std::optional<std::vector<Diag>> Diags;
Index: clang-tools-extra/clangd/ParsedAST.cpp
===================================================================
--- clang-tools-extra/clangd/ParsedAST.cpp
+++ clang-tools-extra/clangd/ParsedAST.cpp
@@ -12,6 +12,7 @@
 #include "../clang-tidy/ClangTidyModule.h"
 #include "../clang-tidy/ClangTidyModuleRegistry.h"
 #include "AST.h"
+#include "ASTMatchers.h"
 #include "Compiler.h"
 #include "Config.h"
 #include "Diagnostics.h"
@@ -50,8 +51,10 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include <iterator>
 #include <memory>
 #include <optional>
+#include <utility>
 #include <vector>
 
 // Force the linker to link in Clang-tidy modules.
@@ -383,12 +386,14 @@
   // dropped later on to not pay for extra latency by processing them.
   DiagnosticConsumer *DiagConsumer = &ASTDiags;
   IgnoreDiagnostics DropDiags;
+  std::vector<MatcherPragma> Matchers;
   if (Preamble) {
     Patch = PreamblePatch::createFullPatch(Filename, Inputs, *Preamble);
     Patch->apply(*CI);
     PreserveDiags = Patch->preserveDiagnostics();
     if (!PreserveDiags)
       DiagConsumer = &DropDiags;
+    Matchers = Preamble->Matchers;
   }
   auto Clang = prepareCompilerInstance(
       std::move(CI), PreamblePCH,
@@ -457,6 +462,7 @@
     const FileEntry *MainFE = SM.getFileEntryForID(SM.getMainFileID());
     Clang->getPreprocessor().getHeaderSearchInfo().MarkFileIncludeOnce(MainFE);
   }
+  MatcherPragma::parse(&Matchers, *Clang);
 
   // Set up ClangTidy. Must happen after BeginSourceFile() so ASTContext exists.
   // Clang-tidy has some limitations to ensure reasonable performance:
@@ -657,6 +663,8 @@
     trace::Span Tracer("ClangTidyMatch");
     CTFinder.matchAST(Clang->getASTContext());
   }
+  MatcherPragma::diagnose(Matchers, Clang->getASTContext(),
+                          Clang->getDiagnostics());
 
   // XXX: This is messy: clang-tidy checks flush some diagnostics at EOF.
   // However Action->EndSourceFile() would destroy the ASTContext!
@@ -684,9 +692,9 @@
   }
   ParsedAST Result(Filename, Inputs.Version, std::move(Preamble),
                    std::move(Clang), std::move(Action), std::move(Tokens),
-                   std::move(Macros), std::move(Marks), std::move(ParsedDecls),
-                   std::move(Diags), std::move(Includes),
-                   std::move(CanonIncludes));
+                   std::move(Macros), std::move(Marks), std::move(Matchers),
+                   std::move(ParsedDecls), std::move(Diags),
+                   std::move(Includes), std::move(CanonIncludes));
   if (Result.Diags)
     llvm::move(issueIncludeCleanerDiagnostics(Result, Inputs.Contents),
                std::back_inserter(*Result.Diags));
@@ -783,13 +791,15 @@
                      std::unique_ptr<FrontendAction> Action,
                      syntax::TokenBuffer Tokens, MainFileMacros Macros,
                      std::vector<PragmaMark> Marks,
+                     std::vector<MatcherPragma> Matchers,
                      std::vector<Decl *> LocalTopLevelDecls,
                      std::optional<std::vector<Diag>> Diags,
                      IncludeStructure Includes, CanonicalIncludes CanonIncludes)
     : TUPath(TUPath), Version(Version), Preamble(std::move(Preamble)),
       Clang(std::move(Clang)), Action(std::move(Action)),
       Tokens(std::move(Tokens)), Macros(std::move(Macros)),
-      Marks(std::move(Marks)), Diags(std::move(Diags)),
+      Marks(std::move(Marks)), Matchers(std::move(Matchers)),
+      Diags(std::move(Diags)),
       LocalTopLevelDecls(std::move(LocalTopLevelDecls)),
       Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)) {
   Resolver = std::make_unique<HeuristicResolver>(getASTContext());
Index: clang-tools-extra/clangd/Diagnostics.cpp
===================================================================
--- clang-tools-extra/clangd/Diagnostics.cpp
+++ clang-tools-extra/clangd/Diagnostics.cpp
@@ -584,6 +584,8 @@
         for (auto &Fix : Diag.Fixes)
           CleanMessage(Fix.Message);
       }
+    } else {
+      Diag.Source = Diag::Clangd;
     }
     setTags(Diag);
   }
Index: clang-tools-extra/clangd/Compiler.cpp
===================================================================
--- clang-tools-extra/clangd/Compiler.cpp
+++ clang-tools-extra/clangd/Compiler.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Compiler.h"
+#include "ASTMatchers.h"
 #include "support/Logger.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Frontend/CompilerInvocation.h"
Index: clang-tools-extra/clangd/CodeComplete.cpp
===================================================================
--- clang-tools-extra/clangd/CodeComplete.cpp
+++ clang-tools-extra/clangd/CodeComplete.cpp
@@ -42,6 +42,7 @@
 #include "support/Trace.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
+#include "clang/ASTMatchers/Dynamic/Parser.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Basic/SourceLocation.h"
@@ -1362,6 +1363,7 @@
         Input.FileName);
     return false;
   }
+  MatcherPragma::parse(/*Matchers=*/nullptr, *Clang);
   // 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)
Index: clang-tools-extra/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/CMakeLists.txt
+++ clang-tools-extra/clangd/CMakeLists.txt
@@ -63,6 +63,7 @@
 
 add_clang_library(clangDaemon
   AST.cpp
+  ASTMatchers.cpp
   ASTSignals.cpp
   ClangdLSPServer.cpp
   ClangdServer.cpp
@@ -160,6 +161,7 @@
   PRIVATE
   clangAST
   clangASTMatchers
+  clangDynamicASTMatchers
   clangBasic
   clangDriver
   clangFormat
Index: clang-tools-extra/clangd/ASTMatchers.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/ASTMatchers.h
@@ -0,0 +1,68 @@
+//===--- ASTMatchers.h - Query the current file with matchers ----*- C++-*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// `#pragma clang query` lets users run AST matchers against the current file.
+// It is named after the clang-query tool and serves a similar purpose.
+//
+// Examples:
+//   // Marks all function parameters
+//   #pragma clang query parmVarDecl()
+//
+//   // Marks the types of function parameters
+//   #pragma clang query parmVarDecl(hasTypeLoc().bind("type"))
+//
+// The results are exposed in two ways:
+//  - matches are shown as warning diagnostics
+//  - find-references on the pragma will show the matches
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_ASTMATCHERS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_ASTMATCHERS_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/Dynamic/VariantValue.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace clang::clangd {
+
+/// A parsed `#pragma clang query <matcher>` directive.
+/// These define AST matchers which can be evaluated against the document.
+struct MatcherPragma {
+  /// The compiled matcher.
+  /// Always valid: in case of errors we produce no MatcherPragma at all.
+  ast_matchers::dynamic::DynTypedMatcher Matcher;
+  /// Closed interval of (1-based) line numbers covered by the pragma.
+  std::pair<unsigned, unsigned> LineRange;
+
+  /// Find ranges of code in the matcher matches, along with their bound names.
+  std::vector<std::pair<llvm::StringRef, SourceRange>>
+  match(ASTContext &) const;
+
+  // Registers a handler to parse `#pragma clang query` directives.
+  //
+  // The AST matchers are written into *Out, if provided.
+  // Problems with the matcher specs are reported as diagnostics.
+  static void parse(std::vector<MatcherPragma> *Out, CompilerInstance &);
+
+  // Issues diagnostics marking the locations of all matchers in the list.
+  static void diagnose(llvm::ArrayRef<MatcherPragma>, ASTContext &,
+                       DiagnosticsEngine &);
+};
+
+} // namespace clang::clangd
+
+#endif
Index: clang-tools-extra/clangd/ASTMatchers.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/ASTMatchers.cpp
@@ -0,0 +1,242 @@
+//===--- ASTMatchers.cpp --------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "ASTMatchers.h"
+#include "SourceCode.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/ASTMatchers/Dynamic/Diagnostics.h"
+#include "clang/ASTMatchers/Dynamic/Parser.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/TokenKinds.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Lex/Pragma.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace clang::clangd {
+namespace {
+
+// The matcher parser reads from a simple buffer (StringRef).
+// But we want to report errors in terms of SourceLocation of the original file.
+// This struct gathers the text, but retains enough information to translate
+// offsets into it back into SourceLocations.
+struct TextFromPragmaTokens {
+  std::string Text;
+  std::vector<std::pair</*Offset*/ unsigned, SourceLocation>> Mapping;
+
+  TextFromPragmaTokens(Preprocessor &PP, SourceLocation Initial) {
+    Mapping.emplace_back(0, Initial);
+    clang::Token Tok;
+    while (1) {
+      PP.LexUnexpandedToken(Tok);
+      if (Tok.is(tok::eod))
+        break;
+      if (Tok.is(tok::code_completion)) {
+        PP.setCodeCompletionReached();
+        break;
+      }
+      Text.push_back(' ');
+      Mapping.emplace_back(Text.size(), Tok.getLocation());
+      Text.append(PP.getSourceManager().getCharacterData(Tok.getLocation()),
+                  Tok.getLength());
+    }
+  }
+
+  // Returns the location corresponding to an offset in the buffer.
+  SourceLocation loc(unsigned Offset) const {
+    auto Result = prevTokenImpl(Offset);
+    return Result.first.getLocWithOffset(Result.second);
+  }
+
+  // Returns the location of the last token <= Offset.
+  SourceLocation prevToken(unsigned Offset) const {
+    return prevTokenImpl(Offset).first;
+  }
+
+  /// Returns the location of the last token.
+  SourceLocation end() const { return Mapping.back().second; }
+
+private:
+  // Returns the token that Offset is part of, and the position with it.
+  std::pair<SourceLocation, unsigned> prevTokenImpl(unsigned Offset) const {
+    // Text:   " tok1 tok2"
+    // Markers: ^^    ^
+    if (Offset == 0)
+      return {Mapping.begin()->second, 0};
+    auto Entry = std::prev(
+        std::partition_point(Mapping.begin(), Mapping.end(), [&](auto &Entry) {
+          return Entry.first <= Offset;
+        }));
+    return {Entry->second, Offset - Entry->first};
+  }
+};
+
+class ASTMatcherParser : public PragmaHandler {
+  std::vector<MatcherPragma> *Out;
+  CompilerInstance &Clang;
+  unsigned DiagMatcherProblem;
+
+public:
+  ASTMatcherParser(std::vector<MatcherPragma> *Out, CompilerInstance &Clang)
+      : PragmaHandler("query"), Out(Out), Clang(Clang),
+        DiagMatcherProblem(Clang.getDiagnostics().getCustomDiagID(
+            DiagnosticsEngine::Error, "Bad matcher: %0")) {}
+
+  void HandlePragma(Preprocessor &PP, PragmaIntroducer PI,
+                    Token &FirstToken) override {
+    auto &SM = PP.getSourceManager();
+    // clangd only respects main-file pragmas (completion+preamble+main parse)
+    if (!isInsideMainFile(PI.Loc, SM))
+      return;
+    TextFromPragmaTokens Text(PP, FirstToken.getLocation());
+    if (PP.isCodeCompletionReached())
+      return handleCompletion(Text.Text);
+    if (!Out)
+      return;
+
+    ast_matchers::dynamic::Diagnostics ParserDiags;
+    llvm::StringRef MatcherText = Text.Text;
+    bool HasBinds = MatcherText.contains(". bind (");
+    if (auto Matcher = ast_matchers::dynamic::Parser::parseMatcherExpression(
+            MatcherText, &ParserDiags)) {
+      // Add "root" binding if there are no binds.
+      if (!HasBinds) {
+        std::string ID =
+            "query:" + std::to_string(SM.getSpellingLineNumber(PI.Loc));
+        if (auto Bound = Matcher->tryBind(ID))
+          Matcher = std::move(Bound);
+      }
+      Out->push_back(
+          MatcherPragma{std::move(*Matcher),
+                        std::make_pair(SM.getSpellingLineNumber(PI.Loc),
+                                       SM.getSpellingLineNumber(Text.end()))});
+    }
+    for (const auto &ParserError : ParserDiags.errors()) {
+      for (const auto &Message : ParserError.Messages) {
+        std::string Msg;
+        {
+          llvm::raw_string_ostream OS(Msg);
+          // FIXME: Only show the message of diag in question!
+          ParserDiags.printToStream(OS);
+        }
+        SourceRange Range(Text.loc(Message.Range.Start.Column - 1),
+                          Text.prevToken(Message.Range.End.Column - 1));
+        Clang.getDiagnostics().Report(Range.getBegin(), DiagMatcherProblem)
+            << Range << Msg;
+      }
+    }
+  }
+
+  void handleCompletion(llvm::StringRef Text) {
+    std::vector<CodeCompletionResult> Results;
+    auto Copy = [&](llvm::StringRef Text) {
+      return Clang.getCodeCompletionConsumer().getAllocator().CopyString(Text);
+    };
+
+    for (auto &Completion :
+         ast_matchers::dynamic::Parser::completeExpression(Text, Text.size())) {
+      clang::CodeCompletionBuilder Builder(
+          Clang.getCodeCompletionConsumer().getAllocator(),
+          Clang.getCodeCompletionConsumer().getCodeCompletionTUInfo());
+      auto [Return, Signature] =
+          llvm::StringRef(Completion.MatcherDecl).split(' ');
+      // The plain Completion.TypedText is a prefix string like "varDecl(".
+      // This is not ideal for LSP, parse the "MatcherDecl" string instead.
+
+      // Matcher<Decl> varDecl(Matcher<VarDecl>...)
+      if (Return.starts_with("Matcher<") && Return.ends_with(">") &&
+          !Signature.empty()) {
+        Builder.AddResultTypeChunk(Copy(Return));
+        if (Signature.ends_with(")")) {
+          auto [Name, Args] = Signature.drop_back().split("(");
+          Builder.AddTypedTextChunk(Copy(Name));
+          Builder.AddChunk(CodeCompletionString::CK_LeftParen, "(");
+          // FIXME: we could add individual arg placeholders.
+          Builder.AddPlaceholderChunk(Copy(Args));
+          Builder.AddChunk(CodeCompletionString::CK_RightParen, ")");
+        } else {
+          Builder.AddTypedTextChunk(Copy(Completion.TypedText));
+        }
+      } else if (Completion.MatcherDecl == "bind") {
+        Builder.AddTypedTextChunk("bind");
+        Builder.AddChunk(CodeCompletionString::CK_LeftParen, "(");
+        Builder.AddChunk(CodeCompletionString::CK_Text, "\"");
+        Builder.AddPlaceholderChunk("name");
+        Builder.AddChunk(CodeCompletionString::CK_Text, "\"");
+        Builder.AddChunk(CodeCompletionString::CK_RightParen, ")");
+      } else {
+        // Generic fallback, we don't expect to get here much.
+        Builder.AddTypedTextChunk(Copy(Completion.TypedText));
+      }
+      Results.push_back(Builder.TakeString());
+    }
+
+    Clang.getCodeCompletionConsumer().ProcessCodeCompleteResults(
+        Clang.getSema(), CodeCompletionContext::CCC_Other, Results.data(),
+        Results.size());
+  }
+};
+
+} // namespace
+
+void MatcherPragma::parse(std::vector<MatcherPragma> *Out,
+                          CompilerInstance &Clang) {
+  Clang.getPreprocessor().AddPragmaHandler("clang",
+                                           new ASTMatcherParser(Out, Clang));
+}
+
+std::vector<std::pair<llvm::StringRef, SourceRange>>
+MatcherPragma::match(ASTContext &Ctx) const {
+  std::vector<std::pair<llvm::StringRef, SourceRange>> Result;
+  for (const auto &Match : ast_matchers::matchDynamic(Matcher, Ctx))
+    for (const auto &KV : Match.getMap())
+      Result.emplace_back(KV.first, KV.second.getSourceRange());
+  return Result;
+}
+
+void MatcherPragma::diagnose(llvm::ArrayRef<MatcherPragma> Matchers,
+                             ASTContext &AST, DiagnosticsEngine &Diags) {
+  ast_matchers::MatchFinder Finder;
+  class ReportMatch : public ast_matchers::MatchFinder::MatchCallback {
+    DiagnosticsEngine &Diags;
+    unsigned DiagBindsHere;
+
+  public:
+    ReportMatch(DiagnosticsEngine &Diags)
+        : Diags(Diags),
+          // FIXME: somehow propagate a sensible LSP severity/source/code.
+          // For now, this is a warning, as we expect notes to have parents.
+          DiagBindsHere(Diags.getCustomDiagID(DiagnosticsEngine::Warning,
+                                              "%0 matches")) {}
+
+    void run(const ast_matchers::MatchFinder::MatchResult &Result) override {
+      for (const auto &E : Result.Nodes.getMap()) {
+        E.second.dump(llvm::errs() << E.first << ": ", *Result.Context);
+        Diags.Report(E.second.getSourceRange().getBegin(), DiagBindsHere)
+            << E.second.getSourceRange() << E.first;
+      }
+    }
+  } Reporter(Diags);
+
+  for (const auto &M : Matchers)
+    Finder.addDynamicMatcher(M.Matcher, &Reporter);
+  Finder.matchAST(AST);
+}
+
+} // namespace clang::clangd
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to