sammccall created this revision.
Herald added subscribers: kadircet, arphaman.
Herald added a project: All.
sammccall requested review of this revision.
Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov.
Herald added a project: clang-tools-extra.

The idea: you can type in AST matchers, and clangd shows you matches
within the document.

Syntax is like:

  #pragma clang query varDecl().bind("var")

Features:

- matches are shown within the document as diagnostics
- find-references on the pragma will produce a list of matches
- errors within matchers are shown as diagnostics
- code completion is available within matchers

There are some rough edges here, e.g. the diagnostics are warnings and
have no LSP diagnostic code, the diagnostic text for matcher errors
isn't ideal etc.
I think this is a good start as-is and we can work out if we want to
improve these cases later, if this feature ends up being useful.


Repository:
  rG LLVM Github Monorepo

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