//===- unittest/Introspection/IntrospectionTest.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 // //===----------------------------------------------------------------------===// // // Tests for AST location API introspection. // //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/NodeIntrospection.h" #include "clang/Tooling/Tooling.h" #include "gmock/gmock-matchers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace clang; using namespace clang::ast_matchers; using namespace clang::tooling; using ::testing::Pair; using ::testing::UnorderedElementsAre; template std::map FormatExpected(const MapType &Accessors) { std::map Result; llvm::transform(llvm::make_filter_range(Accessors, [](const auto &Accessor) { return Accessor.first.isValid(); }), std::inserter(Result, Result.end()), [](const auto &Accessor) { return std::make_pair(LocationCallFormatterCpp::format( *Accessor.second.get()), Accessor.first); }); return Result; } #define STRING_LOCATION_PAIR(INSTANCE, LOC) Pair(#LOC, INSTANCE->LOC) TEST(Introspection, SourceLocations_Stmt) { auto AST = buildASTFromCode("void foo() {} void bar() { foo(); }", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant( callExpr(callee(functionDecl(hasName("foo")))).bind("fooCall"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); auto *FooCall = BoundNodes[0].getNodeAs("fooCall"); auto Result = NodeIntrospection::GetLocations(FooCall); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT( ExpectedLocations, UnorderedElementsAre(STRING_LOCATION_PAIR(FooCall, getBeginLoc()), STRING_LOCATION_PAIR(FooCall, getEndLoc()), STRING_LOCATION_PAIR(FooCall, getExprLoc()), STRING_LOCATION_PAIR(FooCall, getRParenLoc()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT(ExpectedRanges, UnorderedElementsAre(STRING_LOCATION_PAIR( FooCall, getSourceRange()))); } TEST(Introspection, SourceLocations_Decl) { auto AST = buildASTFromCode(R"cpp( namespace ns1 { namespace ns2 { template struct Foo {}; template struct Bar { struct Nested { template Foo method(int i, bool b) const noexcept(true); }; }; } // namespace ns2 } // namespace ns1 template template ns1::ns2::Foo ns1::ns2::Bar::Nested::method(int i, bool b) const noexcept(true) {} )cpp", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant( cxxMethodDecl(hasName("method")).bind("method"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); const auto *MethodDecl = BoundNodes[0].getNodeAs("method"); auto Result = NodeIntrospection::GetLocations(MethodDecl); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT(ExpectedLocations, UnorderedElementsAre( STRING_LOCATION_PAIR(MethodDecl, getBeginLoc()), STRING_LOCATION_PAIR(MethodDecl, getBodyRBrace()), STRING_LOCATION_PAIR(MethodDecl, getInnerLocStart()), STRING_LOCATION_PAIR(MethodDecl, getLocation()), STRING_LOCATION_PAIR(MethodDecl, getOuterLocStart()), STRING_LOCATION_PAIR(MethodDecl, getTypeSpecEndLoc()), STRING_LOCATION_PAIR(MethodDecl, getTypeSpecStartLoc()), STRING_LOCATION_PAIR(MethodDecl, getEndLoc()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT( ExpectedRanges, UnorderedElementsAre( STRING_LOCATION_PAIR(MethodDecl, getExceptionSpecSourceRange()), STRING_LOCATION_PAIR(MethodDecl, getParametersSourceRange()), STRING_LOCATION_PAIR(MethodDecl, getReturnTypeSourceRange()), STRING_LOCATION_PAIR(MethodDecl, getSourceRange()))); } TEST(Introspection, SourceLocations_NNS) { auto AST = buildASTFromCode(R"cpp( namespace ns { struct A { void foo(); }; } void ns::A::foo() {} )cpp", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant(nestedNameSpecifierLoc().bind("nns"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); const auto *NNS = BoundNodes[0].getNodeAs("nns"); auto Result = NodeIntrospection::GetLocations(NNS); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT( ExpectedLocations, UnorderedElementsAre(STRING_LOCATION_PAIR(NNS, getBeginLoc()), STRING_LOCATION_PAIR(NNS, getEndLoc()), STRING_LOCATION_PAIR(NNS, getLocalBeginLoc()), STRING_LOCATION_PAIR(NNS, getLocalEndLoc()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT( ExpectedRanges, UnorderedElementsAre(STRING_LOCATION_PAIR(NNS, getLocalSourceRange()), STRING_LOCATION_PAIR(NNS, getSourceRange()))); } TEST(Introspection, SourceLocations_TA_Type) { auto AST = buildASTFromCode(R"cpp( template struct A { void foo(); }; void foo() { A a; } )cpp", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant(templateArgumentLoc().bind("ta"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); const auto *TA = BoundNodes[0].getNodeAs("ta"); auto Result = NodeIntrospection::GetLocations(TA); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT(ExpectedLocations, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getLocation()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT(ExpectedRanges, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getSourceRange()))); } TEST(Introspection, SourceLocations_TA_Decl) { auto AST = buildASTFromCode(R"cpp( template void test2() {} void doNothing() {} void test() { test2(); } )cpp", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant(templateArgumentLoc().bind("ta"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); const auto *TA = BoundNodes[0].getNodeAs("ta"); auto Result = NodeIntrospection::GetLocations(TA); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT(ExpectedLocations, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getLocation()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT(ExpectedRanges, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getSourceRange()))); } TEST(Introspection, SourceLocations_TA_Nullptr) { auto AST = buildASTFromCode(R"cpp( template void test2() {} void doNothing() {} void test() { test2(); } )cpp", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant(templateArgumentLoc().bind("ta"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); const auto *TA = BoundNodes[0].getNodeAs("ta"); auto Result = NodeIntrospection::GetLocations(TA); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT(ExpectedLocations, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getLocation()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT(ExpectedRanges, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getSourceRange()))); } TEST(Introspection, SourceLocations_TA_Integral) { auto AST = buildASTFromCode(R"cpp( template void test2() {} void test() { test2<42>(); } )cpp", "foo.cpp", std::make_shared()); auto &Ctx = AST->getASTContext(); auto &TU = *Ctx.getTranslationUnitDecl(); auto BoundNodes = ast_matchers::match( decl(hasDescendant(templateArgumentLoc().bind("ta"))), TU, Ctx); EXPECT_EQ(BoundNodes.size(), 1u); const auto *TA = BoundNodes[0].getNodeAs("ta"); auto Result = NodeIntrospection::GetLocations(TA); if (Result.LocationAccessors.empty() && Result.RangeAccessors.empty()) { return; } auto ExpectedLocations = FormatExpected(Result.LocationAccessors); EXPECT_THAT(ExpectedLocations, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getLocation()))); auto ExpectedRanges = FormatExpected(Result.RangeAccessors); EXPECT_THAT(ExpectedRanges, UnorderedElementsAre(STRING_LOCATION_PAIR(TA, getSourceRange()))); } TEST(Introspection, SourceLocations_TA_Template) { auto AST = buildASTFromCode(R"cpp( template class A; template