ilya-biryukov updated this revision to Diff 148006.
ilya-biryukov added a comment.
Herald added a subscriber: mgorny.

- Move helpers tha switch header and source into separate files.
- Also uprank items coming from the matching headers


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D46943

Files:
  clangd/AST.cpp
  clangd/AST.h
  clangd/CMakeLists.txt
  clangd/ClangdServer.cpp
  clangd/CodeComplete.cpp
  clangd/MatchingHeader.cpp
  clangd/MatchingHeader.h
  clangd/Quality.cpp
  clangd/Quality.h
  unittests/clangd/QualityTests.cpp
  unittests/clangd/TestTU.cpp

Index: unittests/clangd/TestTU.cpp
===================================================================
--- unittests/clangd/TestTU.cpp
+++ unittests/clangd/TestTU.cpp
@@ -1,5 +1,4 @@
-//===--- TestTU.cpp - Scratch source files for testing ------------*-
-//C++-*-===//
+//===--- TestTU.cpp - Scratch source files for testing -------------------===//
 //
 //                     The LLVM Compiler Infrastructure
 //
@@ -78,11 +77,11 @@
     auto *ND = dyn_cast<NamedDecl>(D);
     if (!ND || ND->getNameAsString() != QName)
       continue;
-    if (Result) {
+    if (Result && ND->getCanonicalDecl() != Result) {
       ADD_FAILURE() << "Multiple Decls named " << QName;
       assert(false && "QName is not unique");
     }
-    Result = ND;
+    Result = cast<NamedDecl>(ND->getCanonicalDecl());
   }
   if (!Result) {
     ADD_FAILURE() << "No Decl named " << QName << " in AST";
Index: unittests/clangd/QualityTests.cpp
===================================================================
--- unittests/clangd/QualityTests.cpp
+++ unittests/clangd/QualityTests.cpp
@@ -37,15 +37,15 @@
   auto AST = Header.build();
 
   SymbolQualitySignals Quality;
-  Quality.merge(findSymbol(Symbols, "x"));
+  Quality.merge(findSymbol(Symbols, "x"), /*MainFilename=*/"foo.cpp");
   EXPECT_FALSE(Quality.Deprecated);
   EXPECT_EQ(Quality.SemaCCPriority, SymbolQualitySignals().SemaCCPriority);
   EXPECT_EQ(Quality.References, SymbolQualitySignals().References);
 
   Symbol F = findSymbol(Symbols, "f");
   F.References = 24; // TestTU doesn't count references, so fake it.
   Quality = {};
-  Quality.merge(F);
+  Quality.merge(F, /*MainFilename=*/"foo.cpp");
   EXPECT_FALSE(Quality.Deprecated); // FIXME: Include deprecated bit in index.
   EXPECT_EQ(Quality.SemaCCPriority, SymbolQualitySignals().SemaCCPriority);
   EXPECT_EQ(Quality.References, 24u);
@@ -118,6 +118,45 @@
   EXPECT_LT(sortText(0, "a"), sortText(0, "z"));
 }
 
+TEST(QualityTests, BoostCurrentFileDecls) {
+  TestTU Test;
+  Test.HeaderFilename = "foo.h";
+  Test.HeaderCode = R"cpp(
+    int test_func_in_header();
+    int test_func_in_header_and_cpp();
+    )cpp";
+  Test.Code = R"cpp(
+    #include "foo.h"
+    int ::test_func_in_header_and_cpp() {
+    }
+    int test_func_in_cpp();
+
+    int test() {
+      test_func_^
+    }
+  )cpp";
+
+  ParsedAST AST = Test.build();
+
+  SymbolQualitySignals FuncInCpp;
+  FuncInCpp.merge(CodeCompletionResult(&findDecl(AST, "test_func_in_cpp"),
+                                       CCP_Declaration));
+  EXPECT_TRUE(FuncInCpp.AllDeclsInMainFile);
+
+  SymbolQualitySignals FuncInHeader;
+  FuncInHeader.merge(CodeCompletionResult(&findDecl(AST, "test_func_in_header"),
+                                          CCP_Declaration));
+  EXPECT_FALSE(FuncInHeader.AllDeclsInMainFile);
+
+  SymbolQualitySignals FuncInHeaderAndCpp;
+  FuncInHeaderAndCpp.merge(CodeCompletionResult(
+      &findDecl(AST, "test_func_in_header_and_cpp"), CCP_Declaration));
+  EXPECT_FALSE(FuncInHeaderAndCpp.AllDeclsInMainFile);
+
+  EXPECT_LE(FuncInHeader.evaluate(), FuncInCpp.evaluate());
+  EXPECT_FLOAT_EQ(FuncInHeader.evaluate(), FuncInHeaderAndCpp.evaluate());
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/Quality.h
===================================================================
--- clangd/Quality.h
+++ clangd/Quality.h
@@ -47,11 +47,13 @@
   unsigned SemaCCPriority = 0; // 1-80, 1 is best. 0 means absent.
                                // FIXME: this is actually a mix of symbol
                                //        quality and relevance. Untangle this.
+  bool AllDeclsInMainFile = true;
+  bool HasDeclsInMatchingHeader = false;
   bool Deprecated = false;
   unsigned References = 0;
 
   void merge(const CodeCompletionResult &SemaCCResult);
-  void merge(const Symbol &IndexResult);
+  void merge(const Symbol &IndexResult, llvm::StringRef MainFilename);
 
   // Condense these signals down to a single number, higher is better.
   float evaluate() const;
Index: clangd/Quality.cpp
===================================================================
--- clangd/Quality.cpp
+++ clangd/Quality.cpp
@@ -7,7 +7,13 @@
 //
 //===---------------------------------------------------------------------===//
 #include "Quality.h"
+#include "AST.h"
+#include "Logger.h"
+#include "MatchingHeader.h"
+#include "URI.h"
 #include "index/Index.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Basic/SourceManager.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/MathExtras.h"
@@ -17,15 +23,62 @@
 namespace clangd {
 using namespace llvm;
 
+namespace {
+struct DeclFiles {
+  bool AllDeclsInMainFile = false;
+  bool HasDeclsInMatchingHeader = false;
+};
+
+static DeclFiles computeDeclFiles(const Decl *D) {
+  DeclFiles R;
+  R.AllDeclsInMainFile = true;
+  R.HasDeclsInMatchingHeader = false;
+
+  const SourceManager &SM = D->getASTContext().getSourceManager();
+  auto MainFile = SM.getFileEntryForID(SM.getMainFileID());
+  assert(MainFile);
+  for (auto *Redecl : D->redecls()) {
+    auto Loc = SM.getSpellingLoc(Redecl->getLocation());
+    if (SM.isWrittenInMainFile(Loc))
+      continue;
+    R.AllDeclsInMainFile = false;
+
+    auto FileName = SM.getFilename(Loc);
+    if (isMainHeaderFor(FileName, MainFile->getName()))
+      R.HasDeclsInMatchingHeader = true;
+  }
+  return R;
+}
+} // namespace
+
 void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
   SemaCCPriority = SemaCCResult.Priority;
+  if (SemaCCResult.Declaration) {
+    auto DF = computeDeclFiles(SemaCCResult.Declaration);
 
+    AllDeclsInMainFile &= DF.AllDeclsInMainFile;
+    HasDeclsInMatchingHeader |= DF.HasDeclsInMatchingHeader;
+  }
   if (SemaCCResult.Availability == CXAvailability_Deprecated)
     Deprecated = true;
 }
 
-void SymbolQualitySignals::merge(const Symbol &IndexResult) {
+void SymbolQualitySignals::merge(const Symbol &IndexResult,
+                                 llvm::StringRef MainFilename) {
   References = std::max(IndexResult.References, References);
+
+  AllDeclsInMainFile = false;
+  if (IndexResult.CanonicalDeclaration.FileURI.empty())
+    return;
+  // FIXME: can we avoid parsing URIs here?
+  auto FileURI = URI::parse(IndexResult.CanonicalDeclaration.FileURI);
+  if (!FileURI) {
+    log(llvm::formatv("Error while parsing URI: {0}",
+                      llvm::toString(FileURI.takeError())));
+    return;
+  }
+  if (isMainHeaderFor(FileURI->body(), MainFilename))
+    HasDeclsInMatchingHeader = true;
 }
 
 float SymbolQualitySignals::evaluate() const {
@@ -41,6 +94,10 @@
     // Priority 80 is a really bad score.
     Score *= 2 - std::min<float>(80, SemaCCPriority) / 40;
 
+  // Things declared in the main file get a large boost.
+  if (HasDeclsInMatchingHeader || AllDeclsInMainFile)
+    Score *= 2;
+
   if (Deprecated)
     Score *= 0.1f;
 
Index: clangd/MatchingHeader.h
===================================================================
--- /dev/null
+++ clangd/MatchingHeader.h
@@ -0,0 +1,35 @@
+//===--- MatchingHeader.h - Match source and header files --------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// Heuristics to match source files with headers and vice versa.
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MATCHINGHEADER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MATCHINGHEADER_H
+
+#include "Path.h"
+#include "clang/Basic/VirtualFileSystem.h"
+#include "llvm/ADT/Optional.h"
+
+namespace clang {
+
+namespace clangd {
+
+/// Returns true iff the heuristics determine that \p Header is a matching
+/// header for a \p Source.
+bool isMainHeaderFor(PathRef Header, PathRef Source);
+
+/// Attempt to find an existing matching header or source file for \p P.
+/// All files accesses are performed via the provided vfs.
+llvm::Optional<Path>
+findMatchingHeaderOrSource(PathRef P,
+                           llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS);
+
+} // namespace clangd
+} // namespace clang
+
+#endif
\ No newline at end of file
Index: clangd/MatchingHeader.cpp
===================================================================
--- /dev/null
+++ clangd/MatchingHeader.cpp
@@ -0,0 +1,83 @@
+//===--- MatchingHeader.h - Match source and header files -----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "MatchingHeader.h"
+#include "llvm/Support/Path.h"
+
+namespace clang {
+namespace clangd {
+
+namespace {
+constexpr llvm::StringLiteral SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
+                                                    ".c++", ".m", ".mm"};
+constexpr llvm::StringLiteral HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx",
+                                                    ".inc"};
+bool isSourceFile(PathRef P) {
+  StringRef Ext = llvm::sys::path::extension(P);
+  auto SourceIter = std::find_if(
+      std::begin(SourceExtensions), std::end(SourceExtensions),
+      [&Ext](PathRef SourceExt) { return SourceExt.equals_lower(Ext); });
+  return SourceIter != std::end(SourceExtensions);
+}
+
+bool isHeaderFile(PathRef P) {
+  StringRef Ext = llvm::sys::path::extension(P);
+  auto SourceIter = std::find_if(
+      std::begin(HeaderExtensions), std::end(HeaderExtensions),
+      [&Ext](PathRef SourceExt) { return SourceExt.equals_lower(Ext); });
+  return SourceIter != std::end(HeaderExtensions);
+}
+} // namespace
+
+bool isMainHeaderFor(PathRef Header, PathRef Source) {
+  // FIXME: more sophisticated logic could be applied here, e.g. using
+  // clang-format-like regex for matching main files, etc.
+  if (!isSourceFile(Source) || !isHeaderFile(Header))
+    return false;
+  auto SourceNoExt = llvm::sys::path::filename(Source);
+  auto HeaderNoExt = llvm::sys::path::filename(Header);
+  return SourceNoExt.equals_lower(HeaderNoExt);
+}
+
+llvm::Optional<Path>
+findMatchingHeaderOrSource(PathRef P,
+                           llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS) {
+  bool IsSource = isSourceFile(P);
+  bool IsHeader = isHeaderFile(P);
+  // We can only switch between the known extensions.
+  if (!IsSource && !IsHeader)
+    return llvm::None;
+
+  // Array to lookup extensions for the switch. An opposite of where original
+  // extension was found.
+  ArrayRef<llvm::StringLiteral> NewExts;
+  if (IsSource)
+    NewExts = HeaderExtensions;
+  else
+    NewExts = SourceExtensions;
+
+  // Storage for the new path.
+  SmallString<128> NewPath = P;
+  // Loop through switched extension candidates.
+  for (StringRef NewExt : NewExts) {
+    llvm::sys::path::replace_extension(NewPath, NewExt);
+    if (FS->exists(NewPath))
+      return NewPath.str().str(); // First str() to convert from SmallString to
+                                  // StringRef, second to convert from StringRef
+                                  // to std::string
+
+    // Also check NewExt in upper-case, just in case.
+    llvm::sys::path::replace_extension(NewPath, NewExt.upper());
+    if (FS->exists(NewPath))
+      return NewPath.str().str();
+  }
+
+  return llvm::None;
+}
+} // namespace clangd
+} // namespace clang
\ No newline at end of file
Index: clangd/CodeComplete.cpp
===================================================================
--- clangd/CodeComplete.cpp
+++ clangd/CodeComplete.cpp
@@ -988,7 +988,7 @@
     else
       return;
     if (IndexResult)
-      Quality.merge(*IndexResult);
+      Quality.merge(*IndexResult, FileName);
     if (SemaResult) {
       Quality.merge(*SemaResult);
       Relevance.merge(*SemaResult);
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -11,6 +11,7 @@
 #include "CodeComplete.h"
 #include "FindSymbols.h"
 #include "Headers.h"
+#include "MatchingHeader.h"
 #include "SourceCode.h"
 #include "XRefs.h"
 #include "index/Merge.h"
@@ -307,62 +308,7 @@
 }
 
 llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
-
-  StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
-                                  ".c++", ".m", ".mm"};
-  StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
-
-  StringRef PathExt = llvm::sys::path::extension(Path);
-
-  // Lookup in a list of known extensions.
-  auto SourceIter =
-      std::find_if(std::begin(SourceExtensions), std::end(SourceExtensions),
-                   [&PathExt](PathRef SourceExt) {
-                     return SourceExt.equals_lower(PathExt);
-                   });
-  bool IsSource = SourceIter != std::end(SourceExtensions);
-
-  auto HeaderIter =
-      std::find_if(std::begin(HeaderExtensions), std::end(HeaderExtensions),
-                   [&PathExt](PathRef HeaderExt) {
-                     return HeaderExt.equals_lower(PathExt);
-                   });
-
-  bool IsHeader = HeaderIter != std::end(HeaderExtensions);
-
-  // We can only switch between the known extensions.
-  if (!IsSource && !IsHeader)
-    return llvm::None;
-
-  // Array to lookup extensions for the switch. An opposite of where original
-  // extension was found.
-  ArrayRef<StringRef> NewExts;
-  if (IsSource)
-    NewExts = HeaderExtensions;
-  else
-    NewExts = SourceExtensions;
-
-  // Storage for the new path.
-  SmallString<128> NewPath = StringRef(Path);
-
-  // Instance of vfs::FileSystem, used for file existence checks.
-  auto FS = FSProvider.getFileSystem();
-
-  // Loop through switched extension candidates.
-  for (StringRef NewExt : NewExts) {
-    llvm::sys::path::replace_extension(NewPath, NewExt);
-    if (FS->exists(NewPath))
-      return NewPath.str().str(); // First str() to convert from SmallString to
-                                  // StringRef, second to convert from StringRef
-                                  // to std::string
-
-    // Also check NewExt in upper-case, just in case.
-    llvm::sys::path::replace_extension(NewPath, NewExt.upper());
-    if (FS->exists(NewPath))
-      return NewPath.str().str();
-  }
-
-  return llvm::None;
+  return findMatchingHeaderOrSource(Path, FSProvider.getFileSystem());
 }
 
 llvm::Expected<tooling::Replacements>
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -22,6 +22,7 @@
   FindSymbols.cpp
   FuzzyMatch.cpp
   GlobalCompilationDatabase.cpp
+  MatchingHeader.cpp
   Headers.cpp
   JSONExpr.cpp
   JSONRPCDispatcher.cpp
Index: clangd/AST.h
===================================================================
--- clangd/AST.h
+++ clangd/AST.h
@@ -26,7 +26,7 @@
 ///
 /// The returned location is usually the spelling location where the name of the
 /// decl occurs in the code.
-SourceLocation findNameLoc(const clang::Decl* D);
+SourceLocation findNameLoc(const Decl *D);
 
 } // namespace clangd
 } // namespace clang
Index: clangd/AST.cpp
===================================================================
--- clangd/AST.cpp
+++ clangd/AST.cpp
@@ -17,8 +17,8 @@
 namespace clangd {
 using namespace llvm;
 
-SourceLocation findNameLoc(const clang::Decl* D) {
-  const auto& SM = D->getASTContext().getSourceManager();
+SourceLocation findNameLoc(const Decl *D) {
+  const auto &SM = D->getASTContext().getSourceManager();
   // FIXME: Revisit the strategy, the heuristic is limitted when handling
   // macros, we should use the location where the whole definition occurs.
   SourceLocation SpellingLoc = SM.getSpellingLoc(D->getLocation());
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to