[clang][modules] Don't prevent translation of FW_Private includes when explicitly...
[llvm-project.git] / clang-tools-extra / clangd / unittests / IncludeCleanerTests.cpp
blob5a6524dec2f09a6a41182d3ff0c3e11e1519b469
1 //===--- IncludeCleanerTests.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 //===----------------------------------------------------------------------===//
9 #include "Annotations.h"
10 #include "Diagnostics.h"
11 #include "IncludeCleaner.h"
12 #include "ParsedAST.h"
13 #include "SourceCode.h"
14 #include "TestFS.h"
15 #include "TestTU.h"
16 #include "clang-include-cleaner/Analysis.h"
17 #include "clang-include-cleaner/Types.h"
18 #include "clang/AST/DeclBase.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Tooling/Syntax/Tokens.h"
21 #include "llvm/ADT/ArrayRef.h"
22 #include "llvm/ADT/ScopeExit.h"
23 #include "llvm/ADT/StringMap.h"
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Support/Casting.h"
26 #include "llvm/Support/Error.h"
27 #include "llvm/Support/ScopedPrinter.h"
28 #include "gmock/gmock.h"
29 #include "gtest/gtest.h"
30 #include <cstddef>
31 #include <optional>
32 #include <string>
33 #include <vector>
35 namespace clang {
36 namespace clangd {
37 namespace {
39 using ::testing::AllOf;
40 using ::testing::ElementsAre;
41 using ::testing::IsEmpty;
42 using ::testing::Matcher;
43 using ::testing::Pointee;
44 using ::testing::UnorderedElementsAre;
46 Matcher<const Diag &>
47 withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
48 return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
51 MATCHER_P2(Diag, Range, Message,
52 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
53 return arg.Range == Range && arg.Message == Message;
56 MATCHER_P3(Fix, Range, Replacement, Message,
57 "Fix " + llvm::to_string(Range) + " => " +
58 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
59 return arg.Message == Message && arg.Edits.size() == 1 &&
60 arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
62 MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
64 std::string guard(llvm::StringRef Code) {
65 return "#pragma once\n" + Code.str();
68 MATCHER_P(writtenInclusion, Written, "") {
69 if (arg.Written != Written)
70 *result_listener << arg.Written;
71 return arg.Written == Written;
74 TEST(IncludeCleaner, StdlibUnused) {
75 auto TU = TestTU::withCode(R"cpp(
76 #include <list>
77 #include <queue>
78 #include <vector> // IWYU pragma: keep
79 #include <string> // IWYU pragma: export
80 std::list<int> x;
81 )cpp");
82 // Layout of std library impl is not relevant.
83 TU.AdditionalFiles["bits"] = R"cpp(
84 #pragma once
85 namespace std {
86 template <typename> class list {};
87 template <typename> class queue {};
88 template <typename> class vector {};
90 )cpp";
91 TU.AdditionalFiles["list"] = guard("#include <bits>");
92 TU.AdditionalFiles["queue"] = guard("#include <bits>");
93 TU.AdditionalFiles["vector"] = guard("#include <bits>");
94 TU.AdditionalFiles["string"] = guard("#include <bits>");
95 TU.ExtraArgs = {"-isystem", testRoot()};
96 auto AST = TU.build();
97 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
98 EXPECT_THAT(Findings.UnusedIncludes,
99 ElementsAre(Pointee(writtenInclusion("<queue>"))));
102 TEST(IncludeCleaner, GetUnusedHeaders) {
103 llvm::StringLiteral MainFile = R"cpp(
104 #include "a.h"
105 #include "b.h"
106 #include "dir/c.h"
107 #include "dir/unused.h"
108 #include "unguarded.h"
109 #include "unused.h"
110 #include <system_header.h>
111 void foo() {
112 a();
113 b();
114 c();
115 })cpp";
116 // Build expected ast with symbols coming from headers.
117 TestTU TU;
118 TU.Filename = "foo.cpp";
119 TU.AdditionalFiles["foo.h"] = guard("void foo();");
120 TU.AdditionalFiles["a.h"] = guard("void a();");
121 TU.AdditionalFiles["b.h"] = guard("void b();");
122 TU.AdditionalFiles["dir/c.h"] = guard("void c();");
123 TU.AdditionalFiles["unused.h"] = guard("void unused();");
124 TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
125 TU.AdditionalFiles["system/system_header.h"] = guard("");
126 TU.AdditionalFiles["unguarded.h"] = "";
127 TU.ExtraArgs.push_back("-I" + testPath("dir"));
128 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
129 TU.Code = MainFile.str();
130 ParsedAST AST = TU.build();
131 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
132 EXPECT_THAT(
133 Findings.UnusedIncludes,
134 UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
135 Pointee(writtenInclusion("\"dir/unused.h\""))));
138 TEST(IncludeCleaner, ComputeMissingHeaders) {
139 Annotations MainFile(R"cpp(
140 #include "a.h"
142 void foo() {
143 $b[[b]]();
144 })cpp");
145 TestTU TU;
146 TU.Filename = "foo.cpp";
147 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
148 TU.AdditionalFiles["b.h"] = guard("void b();");
150 TU.Code = MainFile.code();
151 ParsedAST AST = TU.build();
153 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
154 const SourceManager &SM = AST.getSourceManager();
155 const NamedDecl *BDecl = nullptr;
156 for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) {
157 const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D);
158 std::string Name = CandidateDecl->getQualifiedNameAsString();
159 if (Name != "b")
160 continue;
161 BDecl = CandidateDecl;
163 ASSERT_TRUE(BDecl);
164 include_cleaner::Symbol B{*BDecl};
165 auto Range = MainFile.range("b");
166 size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start));
167 size_t End = llvm::cantFail(positionToOffset(MainFile.code(), Range.end));
168 syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start),
169 static_cast<unsigned int>(End)};
170 include_cleaner::Header Header{
171 *SM.getFileManager().getOptionalFileRef("b.h")};
172 MissingIncludeDiagInfo BInfo{B, BRange, {Header}};
173 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
176 TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
177 Annotations MainFile(R"cpp(
178 #include "a.h"
179 #include "all.h"
180 $insert_b[[]]#include "baz.h"
181 #include "dir/c.h"
182 $insert_d[[]]$insert_foo[[]]#include "fuzz.h"
183 #include "header.h"
184 $insert_foobar[[]]#include <e.h>
185 $insert_f[[]]$insert_vector[[]]
187 #define DEF(X) const Foo *X;
188 #define BAZ(X) const X x
190 // No missing include insertion for ambiguous macro refs.
191 #if defined(FOO)
192 #endif
194 void foo() {
195 $b[[b]]();
197 ns::$bar[[Bar]] bar;
198 bar.d();
199 $f[[f]]();
201 // this should not be diagnosed, because it's ignored in the config
202 buzz();
204 $foobar[[foobar]]();
206 std::$vector[[vector]] v;
208 int var = $FOO[[FOO]];
210 $DEF[[DEF]](a);
212 $BAR[[BAR]](b);
214 BAZ($Foo[[Foo]]);
215 })cpp");
217 TestTU TU;
218 TU.Filename = "main.cpp";
219 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
220 TU.AdditionalFiles["b.h"] = guard("void b();");
222 TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");
223 TU.AdditionalFiles["dir/d.h"] =
224 guard("namespace ns { struct Bar { void d(); }; }");
226 TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");
227 TU.AdditionalFiles["system/f.h"] = guard("void f();");
228 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
230 TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\"");
231 TU.AdditionalFiles["buzz.h"] = guard("void buzz();");
233 TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\"");
234 TU.AdditionalFiles["private.h"] = guard(R"cpp(
235 // IWYU pragma: private, include "public.h"
236 void foobar();
237 )cpp");
238 TU.AdditionalFiles["header.h"] = guard(R"cpp(
239 namespace std { class vector {}; }
240 )cpp");
242 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
243 TU.AdditionalFiles["foo.h"] = guard(R"cpp(
244 #define BAR(x) Foo *x
245 #define FOO 1
246 struct Foo{};
247 )cpp");
249 TU.Code = MainFile.code();
250 ParsedAST AST = TU.build();
252 auto Findings = computeIncludeCleanerFindings(AST);
253 Findings.UnusedIncludes.clear();
254 std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
255 AST, TU.Code, Findings,
256 {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }});
257 EXPECT_THAT(
258 Diags,
259 UnorderedElementsAre(
260 AllOf(Diag(MainFile.range("b"),
261 "No header providing \"b\" is directly included"),
262 withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
263 "#include \"b.h\""),
264 FixMessage("add all missing includes")})),
265 AllOf(Diag(MainFile.range("bar"),
266 "No header providing \"ns::Bar\" is directly included"),
267 withFix({Fix(MainFile.range("insert_d"),
268 "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
269 FixMessage("add all missing includes")})),
270 AllOf(Diag(MainFile.range("f"),
271 "No header providing \"f\" is directly included"),
272 withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
273 "#include <f.h>"),
274 FixMessage("add all missing includes")})),
275 AllOf(
276 Diag(MainFile.range("foobar"),
277 "No header providing \"foobar\" is directly included"),
278 withFix({Fix(MainFile.range("insert_foobar"),
279 "#include \"public.h\"\n", "#include \"public.h\""),
280 FixMessage("add all missing includes")})),
281 AllOf(
282 Diag(MainFile.range("vector"),
283 "No header providing \"std::vector\" is directly included"),
284 withFix({
285 Fix(MainFile.range("insert_vector"), "#include <vector>\n",
286 "#include <vector>"),
287 FixMessage("add all missing includes"),
288 })),
289 AllOf(Diag(MainFile.range("FOO"),
290 "No header providing \"FOO\" is directly included"),
291 withFix({Fix(MainFile.range("insert_foo"),
292 "#include \"foo.h\"\n", "#include \"foo.h\""),
293 FixMessage("add all missing includes")})),
294 AllOf(Diag(MainFile.range("DEF"),
295 "No header providing \"Foo\" is directly included"),
296 withFix({Fix(MainFile.range("insert_foo"),
297 "#include \"foo.h\"\n", "#include \"foo.h\""),
298 FixMessage("add all missing includes")})),
299 AllOf(Diag(MainFile.range("BAR"),
300 "No header providing \"BAR\" is directly included"),
301 withFix({Fix(MainFile.range("insert_foo"),
302 "#include \"foo.h\"\n", "#include \"foo.h\""),
303 FixMessage("add all missing includes")})),
304 AllOf(Diag(MainFile.range("Foo"),
305 "No header providing \"Foo\" is directly included"),
306 withFix({Fix(MainFile.range("insert_foo"),
307 "#include \"foo.h\"\n", "#include \"foo.h\""),
308 FixMessage("add all missing includes")}))));
311 TEST(IncludeCleaner, IWYUPragmas) {
312 TestTU TU;
313 TU.Code = R"cpp(
314 #include "behind_keep.h" // IWYU pragma: keep
315 #include "exported.h" // IWYU pragma: export
316 #include "public.h"
318 void bar() { foo(); }
319 )cpp";
320 TU.AdditionalFiles["behind_keep.h"] = guard("");
321 TU.AdditionalFiles["exported.h"] = guard("");
322 TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");
323 TU.AdditionalFiles["private.h"] = guard(R"cpp(
324 // IWYU pragma: private, include "public.h"
325 void foo() {}
326 )cpp");
327 ParsedAST AST = TU.build();
328 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
329 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
332 TEST(IncludeCleaner, IWYUPragmaExport) {
333 TestTU TU;
334 TU.Code = R"cpp(
335 #include "foo.h"
336 )cpp";
337 TU.AdditionalFiles["foo.h"] = R"cpp(
338 #ifndef FOO_H
339 #define FOO_H
341 #include "bar.h" // IWYU pragma: export
343 #endif
344 )cpp";
345 TU.AdditionalFiles["bar.h"] = guard(R"cpp(
346 void bar() {}
347 )cpp");
348 ParsedAST AST = TU.build();
350 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
351 EXPECT_THAT(Findings.UnusedIncludes,
352 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
355 TEST(IncludeCleaner, NoDiagsForObjC) {
356 TestTU TU;
357 TU.Code = R"cpp(
358 #include "foo.h"
360 void bar() {}
361 )cpp";
362 TU.AdditionalFiles["foo.h"] = R"cpp(
363 #ifndef FOO_H
364 #define FOO_H
366 #endif
367 )cpp";
368 TU.ExtraArgs.emplace_back("-xobjective-c");
370 ParsedAST AST = TU.build();
371 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
372 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
373 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
376 TEST(IncludeCleaner, UmbrellaUsesPrivate) {
377 TestTU TU;
378 TU.Code = R"cpp(
379 #include "private.h"
380 )cpp";
381 TU.AdditionalFiles["private.h"] = guard(R"cpp(
382 // IWYU pragma: private, include "public.h"
383 void foo() {}
384 )cpp");
385 TU.Filename = "public.h";
386 ParsedAST AST = TU.build();
387 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
388 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
391 TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
392 Annotations MainFile(R"cpp(
393 #include "all.h"
394 #define FOO(X) const Foo *X
395 void foo() {
396 #include [["expander.inc"]]
398 )cpp");
400 TestTU TU;
401 TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);");
402 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
403 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
405 TU.Code = MainFile.code();
406 ParsedAST AST = TU.build();
408 auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
409 EXPECT_THAT(Findings, testing::SizeIs(1));
410 auto RefRange = Findings.front().SymRefRange;
411 auto &SM = AST.getSourceManager();
412 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
413 // FIXME: Point at the spelling location, rather than the include.
414 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
417 TEST(IncludeCleaner, MissingIncludesAreUnique) {
418 Annotations MainFile(R"cpp(
419 #include "all.h"
420 FOO([[Foo]]);
421 )cpp");
423 TestTU TU;
424 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
425 TU.AdditionalFiles["all.h"] = guard(R"cpp(
426 #include "foo.h"
427 #define FOO(X) X y; X z
428 )cpp");
430 TU.Code = MainFile.code();
431 ParsedAST AST = TU.build();
433 auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
434 EXPECT_THAT(Findings, testing::SizeIs(1));
435 auto RefRange = Findings.front().SymRefRange;
436 auto &SM = AST.getSourceManager();
437 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
438 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
441 TEST(IncludeCleaner, NoCrash) {
442 TestTU TU;
443 Annotations MainCode(R"cpp(
444 #include "all.h"
445 void test() {
446 [[1s]];
448 )cpp");
449 TU.Code = MainCode.code();
450 TU.AdditionalFiles["foo.h"] =
451 guard("int operator\"\"s(unsigned long long) { return 0; }");
452 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
453 ParsedAST AST = TU.build();
454 const auto &MissingIncludes =
455 computeIncludeCleanerFindings(AST).MissingIncludes;
456 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
457 auto &SM = AST.getSourceManager();
458 EXPECT_EQ(
459 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
460 MainCode.range());
463 TEST(IncludeCleaner, IsPreferredProvider) {
464 auto TU = TestTU::withCode(R"cpp(
465 #include "decl.h"
466 #include "def.h"
467 #include "def.h"
468 )cpp");
469 TU.AdditionalFiles["decl.h"] = "";
470 TU.AdditionalFiles["def.h"] = "";
472 auto AST = TU.build();
473 auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0];
474 auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1];
475 auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2];
477 auto &FM = AST.getSourceManager().getFileManager();
478 auto DeclH = *FM.getOptionalFileRef("decl.h");
479 auto DefH = *FM.getOptionalFileRef("def.h");
481 auto Includes = convertIncludes(AST);
482 std::vector<include_cleaner::Header> Providers = {
483 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
484 EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers));
485 EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers));
486 EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers));
489 TEST(IncludeCleaner, BatchFix) {
490 TestTU TU;
491 TU.Filename = "main.cpp";
492 TU.AdditionalFiles["foo.h"] = guard("class Foo;");
493 TU.AdditionalFiles["bar.h"] = guard("class Bar;");
494 TU.AdditionalFiles["all.h"] = guard(R"cpp(
495 #include "foo.h"
496 #include "bar.h"
497 )cpp");
499 TU.Code = R"cpp(
500 #include "all.h"
502 Foo* foo;
503 )cpp";
504 auto AST = TU.build();
505 EXPECT_THAT(
506 issueIncludeCleanerDiagnostics(AST, TU.Code,
507 computeIncludeCleanerFindings(AST)),
508 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
509 FixMessage("fix all includes")}),
510 withFix({FixMessage("remove #include directive"),
511 FixMessage("fix all includes")})));
513 TU.Code = R"cpp(
514 #include "all.h"
515 #include "bar.h"
517 Foo* foo;
518 )cpp";
519 AST = TU.build();
520 EXPECT_THAT(
521 issueIncludeCleanerDiagnostics(AST, TU.Code,
522 computeIncludeCleanerFindings(AST)),
523 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
524 FixMessage("fix all includes")}),
525 withFix({FixMessage("remove #include directive"),
526 FixMessage("remove all unused includes"),
527 FixMessage("fix all includes")}),
528 withFix({FixMessage("remove #include directive"),
529 FixMessage("remove all unused includes"),
530 FixMessage("fix all includes")})));
532 TU.Code = R"cpp(
533 #include "all.h"
535 Foo* foo;
536 Bar* bar;
537 )cpp";
538 AST = TU.build();
539 EXPECT_THAT(
540 issueIncludeCleanerDiagnostics(AST, TU.Code,
541 computeIncludeCleanerFindings(AST)),
542 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
543 FixMessage("add all missing includes"),
544 FixMessage("fix all includes")}),
545 withFix({FixMessage("#include \"bar.h\""),
546 FixMessage("add all missing includes"),
547 FixMessage("fix all includes")}),
548 withFix({FixMessage("remove #include directive"),
549 FixMessage("fix all includes")})));
552 // In the presence of IWYU pragma private, we should accept spellings other
553 // than the recommended one if they appear to name the same public header.
554 TEST(IncludeCleaner, VerbatimEquivalence) {
555 auto TU = TestTU::withCode(R"cpp(
556 #include "lib/rel/public.h"
557 int x = Public;
558 )cpp");
559 TU.AdditionalFiles["repo/lib/rel/private.h"] = R"cpp(
560 #pragma once
561 // IWYU pragma: private, include "rel/public.h"
562 int Public;
563 )cpp";
564 TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp(
565 #pragma once
566 #include "rel/private.h"
567 )cpp";
569 TU.ExtraArgs.push_back("-Irepo");
570 TU.ExtraArgs.push_back("-Irepo/lib");
572 auto AST = TU.build();
573 auto Findings = computeIncludeCleanerFindings(AST);
574 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
575 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
578 TEST(IncludeCleaner, ResourceDirIsIgnored) {
579 auto TU = TestTU::withCode(R"cpp(
580 #include <amintrin.h>
581 #include <imintrin.h>
582 void baz() {
583 bar();
585 )cpp");
586 TU.ExtraArgs.push_back("-resource-dir");
587 TU.ExtraArgs.push_back(testPath("resources"));
588 TU.AdditionalFiles["resources/include/amintrin.h"] = guard("");
589 TU.AdditionalFiles["resources/include/imintrin.h"] = guard(R"cpp(
590 #include <emintrin.h>
591 )cpp");
592 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R"cpp(
593 void bar();
594 )cpp");
595 auto AST = TU.build();
596 auto Findings = computeIncludeCleanerFindings(AST);
597 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
598 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
601 } // namespace
602 } // namespace clangd
603 } // namespace clang