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 | |