ilya-biryukov updated this revision to Diff 173337.
ilya-biryukov added a comment.

- Simplify the initial implementation
- Rename SType to OpaqueType


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D52273

Files:
  clangd/CMakeLists.txt
  clangd/ExpectedTypes.cpp
  clangd/ExpectedTypes.h
  unittests/clangd/CMakeLists.txt
  unittests/clangd/ExpectedTypeTest.cpp

Index: unittests/clangd/ExpectedTypeTest.cpp
===================================================================
--- /dev/null
+++ unittests/clangd/ExpectedTypeTest.cpp
@@ -0,0 +1,191 @@
+//===-- ExpectedTypeTest.cpp  -----------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangdUnit.h"
+#include "ExpectedTypes.h"
+#include "TestTU.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Type.h"
+#include "llvm/ADT/StringRef.h"
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::testing::Matcher;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+
+class ASTTest : public ::testing::Test {
+protected:
+  void build(llvm::StringRef Code) {
+    assert(!AST && "AST built twice");
+    AST = TestTU::withCode(Code).build();
+  }
+
+  const ValueDecl *decl(llvm::StringRef Name) {
+    return &llvm::cast<ValueDecl>(findDecl(*AST, Name));
+  }
+
+  QualType typeOf(llvm::StringRef Name) {
+    return decl(Name)->getType().getCanonicalType();
+  }
+
+  ASTContext &ASTCtx() { return AST->getASTContext(); }
+
+private:
+  // Set after calling build().
+  llvm::Optional<ParsedAST> AST;
+};
+
+class ConvertibleToMatcher
+    : public ::testing::MatcherInterface<const ValueDecl *> {
+  ASTContext &Ctx;
+  QualType To;
+  OpaqueType PreferredType;
+
+public:
+  ConvertibleToMatcher(ASTContext &Ctx, QualType To)
+      : Ctx(Ctx), To(To.getCanonicalType()),
+        PreferredType(*OpaqueType::fromPreferredType(Ctx, To)) {}
+
+  void DescribeTo(std::ostream *OS) const override {
+    *OS << "Is convertible to type '" << To.getAsString() << "'";
+  }
+
+  bool MatchAndExplain(const ValueDecl *V,
+                       ::testing::MatchResultListener *L) const override {
+    assert(V);
+    assert(&V->getASTContext() == &Ctx && "different ASTs?");
+    auto ConvertibleTo = OpaqueType::fromCompletionResult(
+        Ctx, CodeCompletionResult(V, CCP_Declaration));
+
+    bool Matched = PreferredType == ConvertibleTo;
+    if (L->IsInterested())
+      *L << "Types of source and target "
+         << (Matched ? "matched" : "did not match")
+         << "\n\tTarget type: " << To.getAsString()
+         << "\n\tSource value type: " << V->getType().getAsString();
+    return Matched;
+  }
+};
+
+class ExpectedTypeConversionTest : public ASTTest {
+protected:
+  Matcher<const ValueDecl *> isConvertibleTo(QualType To) {
+    return ::testing::MakeMatcher(new ConvertibleToMatcher(ASTCtx(), To));
+  }
+};
+
+TEST_F(ExpectedTypeConversionTest, BasicTypes) {
+  build(R"cpp(
+    // ints.
+    bool b;
+    int i;
+    unsigned int ui;
+    long long ll;
+
+    // floats.
+    float f;
+    double d;
+
+    // pointers
+    int* iptr;
+    bool* bptr;
+
+    // user-defined types.
+    struct X {};
+    X user_type;
+  )cpp");
+
+  const ValueDecl *Ints[] = {decl("b"), decl("i"), decl("ui"), decl("ll")};
+  const ValueDecl *Floats[] = {decl("f"), decl("d")};
+  const ValueDecl *IntPtr = decl("iptr");
+  const ValueDecl *BoolPtr = decl("bptr");
+  const ValueDecl *UserType = decl("user_type");
+
+  // Split all decls into equivalence groups. Decls inside the same equivalence
+  // group contain items that are convertible between each other.
+  llvm::ArrayRef<const ValueDecl *> EquivGroups[] = {
+      Ints, Floats, llvm::makeArrayRef(IntPtr), llvm::makeArrayRef(BoolPtr),
+      llvm::makeArrayRef(UserType)};
+
+  // Checks decls inside the same equivalence class can convert to each other.
+  for (auto Group : EquivGroups) {
+    for (auto Decl : Group) {
+      for (auto OtherDecl : Group)
+        EXPECT_THAT(Decl, isConvertibleTo(OtherDecl->getType()));
+    }
+  }
+
+  // Checks items from different equivalence classes are not convertible between
+  // each other.
+  for (auto Group : EquivGroups) {
+    for (auto OtherGroup : EquivGroups) {
+      if (Group.data() == OtherGroup.data())
+        continue;
+
+      for (auto Decl1 : Group) {
+        for (auto Decl2 : OtherGroup) {
+          if (Decl1 == Decl2)
+            continue;
+          EXPECT_THAT(Decl1, Not(isConvertibleTo(Decl2->getType())));
+        }
+      }
+    }
+  }
+}
+
+TEST_F(ExpectedTypeConversionTest, FunctionReturns) {
+  build(R"cpp(
+     int returns_int();
+     int* returns_ptr();
+
+     int int_;
+     int* int_ptr;
+  )cpp");
+
+  const ValueDecl *RetInt = decl("returns_int");
+  const ValueDecl *RetPtr = decl("returns_ptr");
+
+  QualType IntTy = typeOf("int_");
+  QualType IntPtrTy = typeOf("int_ptr");
+
+  EXPECT_THAT(RetInt, isConvertibleTo(IntTy));
+  EXPECT_THAT(RetPtr, isConvertibleTo(IntPtrTy));
+
+  EXPECT_THAT(RetInt, Not(isConvertibleTo(IntPtrTy)));
+  EXPECT_THAT(RetPtr, Not(isConvertibleTo(IntTy)));
+}
+
+TEST_F(ExpectedTypeConversionTest, ReferencesDontMatter) {
+  build(R"cpp(
+    int noref;
+    int & ref = noref;
+    const int & const_ref = noref;
+    int && rv_ref = 10;
+  )cpp");
+
+  const ValueDecl *Decls[] = {decl("noref"), decl("ref"), decl("const_ref"),
+                              decl("rv_ref")};
+  for (auto *D : Decls) {
+    for (auto *OtherD : Decls)
+      EXPECT_THAT(D, isConvertibleTo(OtherD->getType()));
+  }
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
Index: unittests/clangd/CMakeLists.txt
===================================================================
--- unittests/clangd/CMakeLists.txt
+++ unittests/clangd/CMakeLists.txt
@@ -19,6 +19,7 @@
   ContextTests.cpp
   DexTests.cpp
   DraftStoreTests.cpp
+  ExpectedTypeTest.cpp
   FileDistanceTests.cpp
   FileIndexTests.cpp
   FindSymbolsTests.cpp
Index: clangd/ExpectedTypes.h
===================================================================
--- /dev/null
+++ clangd/ExpectedTypes.h
@@ -0,0 +1,58 @@
+//===--- ExpectedTypes.h - Simplified C++ types -----------------*- C++-*--===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// A simplified model of C++ types that can be used to check whether they are
+// convertible between each other without looking at the ASTs.
+// Used for code completion ranking.
+//
+// When using clang APIs, we cannot determine if a type coming from an AST is
+// convertible to another type without looking at both types in the same AST.
+// Unfortunately, we do not have ASTs for index-based completion, so we have use
+// a stable encoding for the C++ types and map them into equivalence classes
+// based on convertibility.
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_EXPECTED_TYPES_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_EXPECTED_TYPES_H
+
+#include "clang/AST/Type.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace clang {
+class CodeCompletionResult;
+
+namespace clangd {
+/// An opaque representation of a type that can be computed based on clang AST
+/// and compared for equality. The encoding is stable between different ASTs,
+/// this allows the representation to be stored in the index and compared with
+/// types coming from a different AST later.
+class OpaqueType {
+public:
+  static llvm::Optional<OpaqueType>
+  fromCompletionResult(ASTContext &Ctx, const CodeCompletionResult &R);
+  static llvm::Optional<OpaqueType> fromPreferredType(ASTContext &Ctx,
+                                                      QualType Type);
+
+  /// Get the raw byte representation of the type. Bitwise equality of the raw
+  /// data is equivalent to equality operators of SType itself. The raw
+  /// representation is never empty.
+  llvm::StringRef rawStr() const { return Data; }
+
+  friend bool operator==(const OpaqueType &L, const OpaqueType &R) {
+    return L.Data == R.Data;
+  }
+  friend bool operator!=(const OpaqueType &L, const OpaqueType &R) {
+    return !(L == R);
+  }
+
+private:
+  explicit OpaqueType(std::string Data);
+  std::string Data;
+};
+} // namespace clangd
+} // namespace clang
+#endif
\ No newline at end of file
Index: clangd/ExpectedTypes.cpp
===================================================================
--- /dev/null
+++ clangd/ExpectedTypes.cpp
@@ -0,0 +1,81 @@
+#include "ExpectedTypes.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Type.h"
+#include "clang/Index/USRGeneration.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
+#include "llvm/ADT/STLExtras.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+static llvm::Optional<QualType> toEquivClass(ASTContext &Ctx, QualType T) {
+  if (T.isNull() || T->isDependentType())
+    return llvm::None;
+  T = T.getCanonicalType().getNonReferenceType().getUnqualifiedType();
+  if (T->isIntegerType() && !T->isEnumeralType())
+    return QualType(Ctx.IntTy);
+  if (T->isFloatingType() && !T->isComplexType())
+    return QualType(Ctx.FloatTy);
+  return T;
+}
+
+static llvm::Optional<QualType>
+typeOfCompletion(const CodeCompletionResult &R) {
+  if (!R.Declaration)
+    return llvm::None;
+  auto *VD = llvm::dyn_cast<ValueDecl>(R.Declaration);
+  if (!VD)
+    return llvm::None;
+  QualType T = VD->getType().getCanonicalType().getNonReferenceType();
+  if (!T->isFunctionType())
+    return T;
+  // Functions are a special case. They are completed as 'foo()' and we want to
+  // match their return type rather than the function type itself.
+  // FIXME(ibiryukov): in some cases, we might want to avoid completing `()`
+  // after the function name, e.g. `std::cout << std::endl`.
+  return T->getAs<FunctionType>()->getReturnType().getNonReferenceType();
+}
+
+llvm::Optional<std::string> encodeType(ASTContext &Ctx, QualType T) {
+  assert(!T.isNull());
+  assert(T.isCanonical());
+  assert(!T->isReferenceType());
+
+  llvm::SmallString<128> Out;
+  if (index::generateUSRForType(T, Ctx, Out))
+    return llvm::None;
+  return Out.str().str();
+}
+} // namespace
+
+OpaqueType::OpaqueType(std::string Data) : Data(std::move(Data)) {}
+
+llvm::Optional<OpaqueType> OpaqueType::fromPreferredType(ASTContext &Ctx,
+                                                         QualType Type) {
+  auto T = toEquivClass(Ctx, Type);
+  if (!T)
+    return llvm::None;
+  auto Encoded = encodeType(Ctx, *T);
+  if (!Encoded)
+    return llvm::None;
+  return OpaqueType(*Encoded);
+}
+
+llvm::Optional<OpaqueType>
+OpaqueType::fromCompletionResult(ASTContext &Ctx,
+                                 const CodeCompletionResult &R) {
+  auto T = typeOfCompletion(R);
+  if (!T)
+    return llvm::None;
+  T = toEquivClass(Ctx, *T);
+  if (!T)
+    return llvm::None;
+  auto Encoded = encodeType(Ctx, *T);
+  if (!Encoded)
+    return llvm::None;
+  return OpaqueType(*Encoded);
+}
+
+} // namespace clangd
+} // namespace clang
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -19,6 +19,7 @@
   Context.cpp
   Diagnostics.cpp
   DraftStore.cpp
+  ExpectedTypes.cpp
   FindSymbols.cpp
   FileDistance.cpp
   FS.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to