blob: 44dc8f0c69cf5b819baca36eb35196cd2b9fbc1a [file] [log] [blame]
//===-- ASTTests.cpp --------------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#include "AST.h"
#include "Annotations.h"
#include "ParsedAST.h"
#include "TestTU.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <string>
#include <vector>
namespace clang {
namespace clangd {
namespace {
using testing::Contains;
using testing::Each;
TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
struct Test {
StringRef AnnotatedCode;
const char *DeducedType;
} Tests[] = {
{"^auto i = 0;", "int"},
{"^auto f(){ return 1;};", "int"},
{
R"cpp( // auto on struct in a namespace
namespace ns1 { struct S {}; }
^auto v = ns1::S{};
)cpp",
"ns1::S",
},
{
R"cpp( // decltype on struct
namespace ns1 { struct S {}; }
ns1::S i;
^decltype(i) j;
)cpp",
"ns1::S",
},
{
R"cpp(// decltype(auto) on struct&
namespace ns1 {
struct S {};
} // namespace ns1
ns1::S i;
ns1::S& j = i;
^decltype(auto) k = j;
)cpp",
"ns1::S &",
},
{
R"cpp( // auto on template class
class X;
template<typename T> class Foo {};
^auto v = Foo<X>();
)cpp",
"Foo<class X>",
},
{
R"cpp( // auto on initializer list.
namespace std
{
template<class _E>
class [[initializer_list]] {};
}
^auto i = {1,2};
)cpp",
"class std::initializer_list<int>",
},
{
R"cpp( // auto in function return type with trailing return type
struct Foo {};
^auto test() -> decltype(Foo()) {
return Foo();
}
)cpp",
"struct Foo",
},
{
R"cpp( // decltype in trailing return type
struct Foo {};
auto test() -> ^decltype(Foo()) {
return Foo();
}
)cpp",
"struct Foo",
},
{
R"cpp( // auto in function return type
struct Foo {};
^auto test() {
return Foo();
}
)cpp",
"struct Foo",
},
{
R"cpp( // auto& in function return type
struct Foo {};
^auto& test() {
static Foo x;
return x;
}
)cpp",
"struct Foo",
},
{
R"cpp( // auto* in function return type
struct Foo {};
^auto* test() {
Foo *x;
return x;
}
)cpp",
"struct Foo",
},
{
R"cpp( // const auto& in function return type
struct Foo {};
const ^auto& test() {
static Foo x;
return x;
}
)cpp",
"struct Foo",
},
{
R"cpp( // decltype(auto) in function return (value)
struct Foo {};
^decltype(auto) test() {
return Foo();
}
)cpp",
"struct Foo",
},
{
R"cpp( // decltype(auto) in function return (ref)
struct Foo {};
^decltype(auto) test() {
static Foo x;
return (x);
}
)cpp",
"struct Foo &",
},
{
R"cpp( // decltype(auto) in function return (const ref)
struct Foo {};
^decltype(auto) test() {
static const Foo x;
return (x);
}
)cpp",
"const struct Foo &",
},
{
R"cpp( // auto on alias
struct Foo {};
using Bar = Foo;
^auto x = Bar();
)cpp",
"Bar",
},
};
for (Test T : Tests) {
Annotations File(T.AnnotatedCode);
auto AST = TestTU::withCode(File.code()).build();
SourceManagerForFile SM("foo.cpp", File.code());
SCOPED_TRACE(File.code());
EXPECT_FALSE(File.points().empty());
for (Position Pos : File.points()) {
auto Location = sourceLocationInMainFile(SM.get(), Pos);
ASSERT_TRUE(!!Location) << llvm::toString(Location.takeError());
auto DeducedType = getDeducedType(AST.getASTContext(), *Location);
ASSERT_TRUE(DeducedType);
EXPECT_EQ(DeducedType->getAsString(), T.DeducedType);
}
}
}
TEST(ClangdAST, GetQualification) {
// Tries to insert the decl `Foo` into position of each decl named `insert`.
// This is done to get an appropriate DeclContext for the insertion location.
// Qualifications are the required nested name specifier to spell `Foo` at the
// `insert`ion location.
// VisibleNamespaces are assumed to be visible at every insertion location.
const struct {
llvm::StringRef Test;
std::vector<llvm::StringRef> Qualifications;
std::vector<std::string> VisibleNamespaces;
} Cases[] = {
{
R"cpp(
namespace ns1 { namespace ns2 { class Foo {}; } }
void insert(); // ns1::ns2::Foo
namespace ns1 {
void insert(); // ns2::Foo
namespace ns2 {
void insert(); // Foo
}
using namespace ns2;
void insert(); // Foo
}
using namespace ns1;
void insert(); // ns2::Foo
using namespace ns2;
void insert(); // Foo
)cpp",
{"ns1::ns2::", "ns2::", "", "", "ns2::", ""},
{},
},
{
R"cpp(
namespace ns1 { namespace ns2 { class Bar { void Foo(); }; } }
void insert(); // ns1::ns2::Bar::Foo
namespace ns1 {
void insert(); // ns2::Bar::Foo
namespace ns2 {
void insert(); // Bar::Foo
}
using namespace ns2;
void insert(); // Bar::Foo
}
using namespace ns1;
void insert(); // ns2::Bar::Foo
using namespace ns2;
void insert(); // Bar::Foo
)cpp",
{"ns1::ns2::Bar::", "ns2::Bar::", "Bar::", "Bar::", "ns2::Bar::",
"Bar::"},
{},
},
{
R"cpp(
namespace ns1 { namespace ns2 { void Foo(); } }
void insert(); // ns2::Foo
namespace ns1 {
void insert(); // ns2::Foo
namespace ns2 {
void insert(); // Foo
}
}
)cpp",
{"ns2::", "ns2::", ""},
{"ns1::"},
},
{
R"cpp(
namespace ns {
extern "C" {
typedef int Foo;
}
}
void insert(); // ns::Foo
)cpp",
{"ns::"},
{},
},
};
for (const auto &Case : Cases) {
Annotations Test(Case.Test);
TestTU TU = TestTU::withCode(Test.code());
ParsedAST AST = TU.build();
std::vector<const Decl *> InsertionPoints;
const NamedDecl *TargetDecl;
findDecl(AST, [&](const NamedDecl &ND) {
if (ND.getNameAsString() == "Foo") {
TargetDecl = &ND;
return true;
}
if (ND.getNameAsString() == "insert")
InsertionPoints.push_back(&ND);
return false;
});
ASSERT_EQ(InsertionPoints.size(), Case.Qualifications.size());
for (size_t I = 0, E = InsertionPoints.size(); I != E; ++I) {
const Decl *D = InsertionPoints[I];
if (Case.VisibleNamespaces.empty()) {
EXPECT_EQ(getQualification(AST.getASTContext(),
D->getLexicalDeclContext(), D->getBeginLoc(),
TargetDecl),
Case.Qualifications[I]);
} else {
EXPECT_EQ(getQualification(AST.getASTContext(),
D->getLexicalDeclContext(), TargetDecl,
Case.VisibleNamespaces),
Case.Qualifications[I]);
}
}
}
}
TEST(ClangdAST, PrintType) {
const struct {
llvm::StringRef Test;
std::vector<llvm::StringRef> Types;
} Cases[] = {
{
R"cpp(
namespace ns1 { namespace ns2 { class Foo {}; } }
void insert(); // ns1::ns2::Foo
namespace ns1 {
void insert(); // ns2::Foo
namespace ns2 {
void insert(); // Foo
}
}
)cpp",
{"ns1::ns2::Foo", "ns2::Foo", "Foo"},
},
{
R"cpp(
namespace ns1 {
typedef int Foo;
}
void insert(); // ns1::Foo
namespace ns1 {
void insert(); // Foo
}
)cpp",
{"ns1::Foo", "Foo"},
},
};
for (const auto &Case : Cases) {
Annotations Test(Case.Test);
TestTU TU = TestTU::withCode(Test.code());
ParsedAST AST = TU.build();
std::vector<const DeclContext *> InsertionPoints;
const TypeDecl *TargetDecl = nullptr;
findDecl(AST, [&](const NamedDecl &ND) {
if (ND.getNameAsString() == "Foo") {
if (const auto *TD = llvm::dyn_cast<TypeDecl>(&ND)) {
TargetDecl = TD;
return true;
}
} else if (ND.getNameAsString() == "insert")
InsertionPoints.push_back(ND.getDeclContext());
return false;
});
ASSERT_EQ(InsertionPoints.size(), Case.Types.size());
for (size_t I = 0, E = InsertionPoints.size(); I != E; ++I) {
const auto *DC = InsertionPoints[I];
EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC),
Case.Types[I]);
}
}
}
TEST(ClangdAST, IsDeeplyNested) {
Annotations Test(
R"cpp(
namespace ns {
class Foo {
void bar() {
class Bar {};
}
};
})cpp");
TestTU TU = TestTU::withCode(Test.code());
ParsedAST AST = TU.build();
EXPECT_TRUE(isDeeplyNested(&findUnqualifiedDecl(AST, "Foo"), /*MaxDepth=*/1));
EXPECT_FALSE(
isDeeplyNested(&findUnqualifiedDecl(AST, "Foo"), /*MaxDepth=*/2));
EXPECT_TRUE(isDeeplyNested(&findUnqualifiedDecl(AST, "bar"), /*MaxDepth=*/2));
EXPECT_FALSE(
isDeeplyNested(&findUnqualifiedDecl(AST, "bar"), /*MaxDepth=*/3));
EXPECT_TRUE(isDeeplyNested(&findUnqualifiedDecl(AST, "Bar"), /*MaxDepth=*/3));
EXPECT_FALSE(
isDeeplyNested(&findUnqualifiedDecl(AST, "Bar"), /*MaxDepth=*/4));
}
MATCHER_P(attrKind, K, "") { return arg->getKind() == K; }
MATCHER(implicitAttr, "") { return arg->isImplicit(); }
TEST(ClangdAST, GetAttributes) {
const char *Code = R"cpp(
class X{};
class [[nodiscard]] Y{};
void f(int * a, int * __attribute__((nonnull)) b);
void foo(bool c) {
if (c)
[[unlikely]] return;
}
)cpp";
ParsedAST AST = TestTU::withCode(Code).build();
auto DeclAttrs = [&](llvm::StringRef Name) {
return getAttributes(DynTypedNode::create(findUnqualifiedDecl(AST, Name)));
};
// Implicit attributes may be present (e.g. visibility on windows).
ASSERT_THAT(DeclAttrs("X"), Each(implicitAttr()));
ASSERT_THAT(DeclAttrs("Y"), Contains(attrKind(attr::WarnUnusedResult)));
ASSERT_THAT(DeclAttrs("f"), Each(implicitAttr()));
ASSERT_THAT(DeclAttrs("a"), Each(implicitAttr()));
ASSERT_THAT(DeclAttrs("b"), Contains(attrKind(attr::NonNull)));
Stmt *FooBody = cast<FunctionDecl>(findDecl(AST, "foo")).getBody();
IfStmt *FooIf = cast<IfStmt>(cast<CompoundStmt>(FooBody)->body_front());
ASSERT_THAT(getAttributes(DynTypedNode::create(*FooIf)),
Each(implicitAttr()));
ASSERT_THAT(getAttributes(DynTypedNode::create(*FooIf->getThen())),
Contains(attrKind(attr::Unlikely)));
}
} // namespace
} // namespace clangd
} // namespace clang