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 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 + fromCompletionResult(ASTContext &Ctx, const CodeCompletionResult &R); + static llvm::Optional 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 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 +typeOfCompletion(const CodeCompletionResult &R) { + if (!R.Declaration) + return llvm::None; + auto *VD = llvm::dyn_cast(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()->getReturnType().getNonReferenceType(); +} + +llvm::Optional 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::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::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: 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: 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(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 AST; +}; + +class ConvertibleToMatcher + : public ::testing::MatcherInterface { + 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 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 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