| 1 | //===-- CollectMacrosTests.cpp ----------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | #include "AST.h" |
| 9 | #include "Annotations.h" |
| 10 | #include "CollectMacros.h" |
| 11 | #include "Matchers.h" |
| 12 | #include "SourceCode.h" |
| 13 | #include "TestTU.h" |
| 14 | #include "clang/Basic/SourceLocation.h" |
| 15 | #include "llvm/Support/ScopedPrinter.h" |
| 16 | #include "gmock/gmock.h" |
| 17 | #include "gtest/gtest.h" |
| 18 | #include <vector> |
| 19 | |
| 20 | namespace clang { |
| 21 | namespace clangd { |
| 22 | namespace { |
| 23 | |
| 24 | using testing::UnorderedElementsAreArray; |
| 25 | |
| 26 | MATCHER_P(rangeIs, R, "" ) { |
| 27 | return arg.StartOffset == R.Begin && arg.EndOffset == R.End; |
| 28 | } |
| 29 | MATCHER(isDef, "" ) { return arg.IsDefinition; } |
| 30 | MATCHER(inConditionalDirective, "" ) { return arg.InConditionalDirective; } |
| 31 | |
| 32 | TEST(CollectMainFileMacros, SelectedMacros) { |
| 33 | // References of the same symbol must have the ranges with the same |
| 34 | // name(integer). If there are N different symbols then they must be named |
| 35 | // from 1 to N. Macros for which SymbolID cannot be computed must be named |
| 36 | // "Unknown". The payload of the annotation describes the extra bit |
| 37 | // information of the MacroOccurrence (e.g. $1(def) => IsDefinition). |
| 38 | const char *Tests[] = { |
| 39 | R"cpp(// Macros: Cursor on definition. |
| 40 | #define $1(def)[[FOO]](x,y) (x + y) |
| 41 | int main() { int x = $1[[FOO]]($1[[FOO]](3, 4), $1[[FOO]](5, 6)); } |
| 42 | )cpp" , |
| 43 | R"cpp( |
| 44 | #define $1(def)[[M]](X) X; |
| 45 | #define $2(def)[[abc]] 123 |
| 46 | int s = $1[[M]]($2[[abc]]); |
| 47 | )cpp" , |
| 48 | // FIXME: Locating macro in duplicate definitions doesn't work. Enable |
| 49 | // this once LocateMacro is fixed. |
| 50 | // R"cpp(// Multiple definitions. |
| 51 | // #define $1[[abc]] 1 |
| 52 | // int func1() { int a = $1[[abc]];} |
| 53 | // #undef $1[[abc]] |
| 54 | |
| 55 | // #define $2[[abc]] 2 |
| 56 | // int func2() { int a = $2[[abc]];} |
| 57 | // #undef $2[[abc]] |
| 58 | // )cpp", |
| 59 | R"cpp( |
| 60 | #ifdef $Unknown(condit)[[UNDEFINED]] |
| 61 | #elifdef $Unknown(condit)[[UNDEFINED]] |
| 62 | #endif |
| 63 | |
| 64 | #ifdef $Unknown(condit)[[UNDEFINED]] |
| 65 | #elifndef $Unknown(condit)[[UNDEFINED]] |
| 66 | #endif |
| 67 | |
| 68 | #ifndef $Unknown(condit)[[UNDEFINED]] |
| 69 | #endif |
| 70 | |
| 71 | #if defined($Unknown(condit)[[UNDEFINED]]) |
| 72 | #endif |
| 73 | )cpp" , |
| 74 | R"cpp( |
| 75 | #ifndef $Unknown(condit)[[abc]] |
| 76 | #define $1(def)[[abc]] |
| 77 | #ifdef $1(condit)[[abc]] |
| 78 | #endif |
| 79 | #endif |
| 80 | )cpp" , |
| 81 | R"cpp( |
| 82 | // Macros from token concatenations not included. |
| 83 | #define $1(def)[[CONCAT]](X) X##A() |
| 84 | #define $2(def)[[PREPEND]](X) MACRO##X() |
| 85 | #define $3(def)[[MACROA]]() 123 |
| 86 | int B = $1[[CONCAT]](MACRO); |
| 87 | int D = $2[[PREPEND]](A); |
| 88 | )cpp" , |
| 89 | R"cpp( |
| 90 | #define $1(def)[[MACRO_ARGS2]](X, Y) X Y |
| 91 | #define $3(def)[[BAR]] 1 |
| 92 | #define $2(def)[[FOO]] $3[[BAR]] |
| 93 | int A = $2[[FOO]]; |
| 94 | )cpp" }; |
| 95 | auto ExpectedResults = [](const llvm::Annotations &T, StringRef Name) { |
| 96 | std::vector<Matcher<MacroOccurrence>> ExpectedLocations; |
| 97 | for (const auto &[R, Bits] : T.rangesWithPayload(Name)) { |
| 98 | if (Bits == "def" ) |
| 99 | ExpectedLocations.push_back(x: testing::AllOf(matchers: rangeIs(gmock_p0: R), matchers: isDef())); |
| 100 | else if (Bits == "condit" ) |
| 101 | ExpectedLocations.push_back( |
| 102 | x: testing::AllOf(matchers: rangeIs(gmock_p0: R), matchers: inConditionalDirective())); |
| 103 | else |
| 104 | ExpectedLocations.push_back(x: testing::AllOf(matchers: rangeIs(gmock_p0: R))); |
| 105 | } |
| 106 | return ExpectedLocations; |
| 107 | }; |
| 108 | |
| 109 | for (const char *Test : Tests) { |
| 110 | llvm::Annotations T(Test); |
| 111 | auto Inputs = TestTU::withCode(Code: T.code()); |
| 112 | Inputs.ExtraArgs.push_back(x: "-std=c++2b" ); |
| 113 | auto AST = Inputs.build(); |
| 114 | auto ActualMacroRefs = AST.getMacros(); |
| 115 | auto &SM = AST.getSourceManager(); |
| 116 | auto &PP = AST.getPreprocessor(); |
| 117 | for (const auto &[Name, Ranges] : T.all_ranges()) { |
| 118 | if (Name == "Unknown" ) { |
| 119 | EXPECT_THAT(ActualMacroRefs.UnknownMacros, |
| 120 | UnorderedElementsAreArray(ExpectedResults(T, "Unknown" ))) |
| 121 | << "Unknown macros doesn't match in " << Test; |
| 122 | continue; |
| 123 | } |
| 124 | |
| 125 | auto Loc = sourceLocationInMainFile( |
| 126 | SM, P: offsetToPosition(Code: T.code(), Offset: Ranges.front().Begin)); |
| 127 | ASSERT_TRUE(bool(Loc)); |
| 128 | const auto *Id = syntax::spelledIdentifierTouching(Loc: *Loc, Tokens: AST.getTokens()); |
| 129 | ASSERT_TRUE(Id); |
| 130 | auto Macro = locateMacroAt(SpelledTok: *Id, PP); |
| 131 | assert(Macro); |
| 132 | auto SID = getSymbolID(MacroName: Macro->Name, MI: Macro->Info, SM); |
| 133 | |
| 134 | EXPECT_THAT(ActualMacroRefs.MacroRefs[SID], |
| 135 | UnorderedElementsAreArray(ExpectedResults(T, Name))) |
| 136 | << "Annotation=" << Name << ", MacroName=" << Macro->Name |
| 137 | << ", Test = " << Test; |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | } // namespace |
| 142 | } // namespace clangd |
| 143 | } // namespace clang |
| 144 | |