clang-tools 24.0.0git
PrerequisiteModulesTest.cpp
Go to the documentation of this file.
1//===--------------- PrerequisiteModulesTests.cpp -------------------*- C++
2//-*-===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9
10/// FIXME: Skip testing on windows temporarily due to the different escaping
11/// code mode.
12#ifndef _WIN32
13
14#include "Annotations.h"
15#include "CodeComplete.h"
16#include "Compiler.h"
17#include "ModulesBuilder.h"
18#include "Preamble.h"
19#include "ProjectModules.h"
21#include "TestTU.h"
22#include "XRefs.h"
23#include "support/Path.h"
25#include "clang/Tooling/Tooling.h"
26#include "llvm/ADT/ScopeExit.h"
27#include "llvm/ADT/StringRef.h"
28#include "llvm/Support/Chrono.h"
29#include "llvm/Support/CommandLine.h"
30#include "llvm/Support/FileSystem.h"
31#include "llvm/Support/Process.h"
32#include "llvm/Support/VirtualFileSystem.h"
33#include "llvm/Support/raw_ostream.h"
34#include "llvm/TargetParser/Host.h"
35#include "gmock/gmock.h"
36#include "gtest/gtest.h"
37
38namespace clang::clangd {
39namespace {
40
41MATCHER_P(named, Name, "") { return arg.Name == Name; }
42
43class GlobalScanningCounterProjectModules : public ProjectModules {
44public:
45 GlobalScanningCounterProjectModules(
46 std::unique_ptr<ProjectModules> Underlying, std::atomic<unsigned> &Count)
47 : Underlying(std::move(Underlying)), Count(Count) {}
48
49 std::vector<std::string> getRequiredModules(PathRef File) override {
50 return Underlying->getRequiredModules(File);
51 }
52
53 std::string getModuleNameForSource(PathRef File) override {
54 return Underlying->getModuleNameForSource(File);
55 }
56
57 void setCommandMangler(CommandMangler Mangler) override {
58 Underlying->setCommandMangler(std::move(Mangler));
59 }
60
61 std::string getSourceForModuleName(llvm::StringRef ModuleName,
62 PathRef RequiredSrcFile) override {
63 Count++;
64 return Underlying->getSourceForModuleName(ModuleName, RequiredSrcFile);
65 }
66
67 ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override {
68 return Underlying->getModuleNameState(ModuleName);
69 }
70
71private:
72 std::unique_ptr<ProjectModules> Underlying;
73 std::atomic<unsigned> &Count;
74};
75
76class PerFileModulesCompilationDatabase : public GlobalCompilationDatabase {
77public:
78 PerFileModulesCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
79 : Directory(TestDir), TFS(TFS),
80 ToolingCDB(std::make_shared<IndexedCompilationDatabase>(*this)) {}
81
82 void addFile(llvm::StringRef Path, llvm::StringRef Contents,
83 std::vector<std::string> ExtraFlags = {}) {
84 ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
85
86 SmallString<256> AbsPath(Directory);
87 llvm::sys::path::append(AbsPath, Path);
88
89 ASSERT_FALSE(llvm::sys::fs::create_directories(
90 llvm::sys::path::parent_path(AbsPath)));
91
92 std::error_code EC;
93 llvm::raw_fd_ostream OS(AbsPath, EC);
94 ASSERT_FALSE(EC);
95 OS << Contents;
96
97 std::vector<std::string> CommandLine = {"clang", "-std=c++20", "-c"};
98 CommandLine.insert(CommandLine.end(), ExtraFlags.begin(), ExtraFlags.end());
99 CommandLine.push_back(std::string(AbsPath));
100
101 Commands[maybeCaseFoldPath(AbsPath)] = tooling::CompileCommand(
102 Directory, std::string(AbsPath), std::move(CommandLine), "");
103 Files.push_back(std::string(AbsPath));
104 }
105
106 std::optional<tooling::CompileCommand>
107 getCompileCommand(PathRef File) const override {
108 auto It = Commands.find(maybeCaseFoldPath(File));
109 if (It == Commands.end())
110 return std::nullopt;
111 tooling::CompileCommand Cmd = It->second;
112 if (llvm::any_of(Cmd.CommandLine, [](llvm::StringRef Arg) {
113 return Arg.starts_with("@");
114 })) {
115 auto FS = llvm::vfs::getRealFileSystem();
116 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
117 ? llvm::cl::TokenizeWindowsCommandLine
118 : llvm::cl::TokenizeGNUCommandLine;
119 tooling::addExpandedResponseFiles(Cmd.CommandLine, Cmd.Directory,
120 Tokenizer, *FS);
121 }
122 return Cmd;
123 }
124
125 std::optional<ProjectInfo> getProjectInfo(PathRef) const override {
126 return ProjectInfo{std::string(Directory)};
127 }
128
129 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
130 return clang::clangd::getProjectModules(ToolingCDB, TFS);
131 }
132
133private:
134 class IndexedCompilationDatabase : public tooling::CompilationDatabase {
135 public:
136 IndexedCompilationDatabase(const PerFileModulesCompilationDatabase &CDB)
137 : CDB(CDB) {}
138
139 std::vector<tooling::CompileCommand>
140 getCompileCommands(StringRef FilePath) const override {
141 if (auto Cmd = CDB.getCompileCommand(FilePath))
142 return {*Cmd};
143 return {};
144 }
145
146 std::vector<std::string> getAllFiles() const override { return CDB.Files; }
147
148 private:
149 const PerFileModulesCompilationDatabase &CDB;
150 };
151
152 std::string Directory;
153 const ThreadsafeFS &TFS;
154 llvm::StringMap<tooling::CompileCommand> Commands;
155 std::vector<std::string> Files;
156 std::shared_ptr<IndexedCompilationDatabase> ToolingCDB;
157};
158
159class ModuleUnitRootCompilationDatabase
160 : public PerFileModulesCompilationDatabase {
161public:
162 ModuleUnitRootCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
163 : PerFileModulesCompilationDatabase(TestDir, TFS) {}
164
165 std::optional<ProjectInfo> getProjectInfo(PathRef File) const override {
166 // Treat each module-unit directory as its own project root so tests can
167 // verify that the persistent cache follows the providing module unit.
168 llvm::SmallString<256> Root(File);
169 llvm::sys::path::remove_filename(Root);
170 return ProjectInfo{std::string(Root)};
171 }
172};
173
174class MockDirectoryCompilationDatabase : public MockCompilationDatabase {
175public:
176 MockDirectoryCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
177 : MockCompilationDatabase(TestDir),
178 MockedCDBPtr(std::make_shared<MockClangCompilationDatabase>(*this)),
179 TFS(TFS), GlobalScanningCount(0) {
180 this->ExtraClangFlags.push_back("-std=c++20");
181 this->ExtraClangFlags.push_back("-c");
182 }
183
184 void addFile(llvm::StringRef Path, llvm::StringRef Contents);
185
186 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
187 return std::make_unique<GlobalScanningCounterProjectModules>(
188 clang::clangd::getProjectModules(MockedCDBPtr, TFS),
189 GlobalScanningCount);
190 }
191
192 unsigned getGlobalScanningCount() const { return GlobalScanningCount; }
193
194private:
195 class MockClangCompilationDatabase : public tooling::CompilationDatabase {
196 public:
197 MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB)
198 : MCDB(MCDB) {}
199
200 std::vector<tooling::CompileCommand>
201 getCompileCommands(StringRef FilePath) const override {
202 std::optional<tooling::CompileCommand> Cmd =
203 MCDB.getCompileCommand(FilePath);
204 EXPECT_TRUE(Cmd);
205 return {*Cmd};
206 }
207
208 std::vector<std::string> getAllFiles() const override { return Files; }
209
210 void AddFile(StringRef File) { Files.push_back(File.str()); }
211
212 private:
213 MockDirectoryCompilationDatabase &MCDB;
214 std::vector<std::string> Files;
215 };
216
217 std::shared_ptr<MockClangCompilationDatabase> MockedCDBPtr;
218 const ThreadsafeFS &TFS;
219
220 mutable std::atomic<unsigned> GlobalScanningCount;
221};
222
223// Add files to the working testing directory and the compilation database.
224void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path,
225 llvm::StringRef Contents) {
226 ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
227
228 SmallString<256> AbsPath(Directory);
229 llvm::sys::path::append(AbsPath, Path);
230
231 ASSERT_FALSE(
232 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
233
234 std::error_code EC;
235 llvm::raw_fd_ostream OS(AbsPath, EC);
236 ASSERT_FALSE(EC);
237 OS << Contents;
238
239 MockedCDBPtr->AddFile(Path);
240}
241
242class PrerequisiteModulesTests : public ::testing::Test {
243protected:
244 void SetUp() override {
245 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir));
246 // /var/tmp is a symlink on Mac. Resolve it so we're asserting the right
247 // path.
248 ASSERT_FALSE(llvm::sys::fs::real_path(TestDir, TestDir));
249 }
250
251 void TearDown() override {
252 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir));
253 }
254
255public:
256 // Get the absolute path for file specified by Path under testing working
257 // directory.
258 std::string getFullPath(llvm::StringRef Path) {
259 SmallString<128> Result(TestDir);
260 llvm::sys::path::append(Result, Path);
261 EXPECT_TRUE(llvm::sys::fs::exists(Result.str()));
262 return Result.str().str();
263 }
264
265 ParseInputs getInputs(llvm::StringRef FileName,
266 const GlobalCompilationDatabase &CDB) {
267 std::string FullPathName = getFullPath(FileName);
268
269 ParseInputs Inputs;
270 std::optional<tooling::CompileCommand> Cmd =
271 CDB.getCompileCommand(FullPathName);
272 EXPECT_TRUE(Cmd);
273 Inputs.CompileCommand = std::move(*Cmd);
274 Inputs.TFS = &FS;
275
276 if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName))
277 Inputs.Contents = Contents->get()->getBuffer().str();
278
279 return Inputs;
280 }
281
282 SmallString<256> TestDir;
283 // FIXME: It will be better to use the MockFS if the scanning process and
284 // build module process doesn't depend on reading real IO.
285 RealThreadsafeFS FS;
286
287 DiagnosticConsumer DiagConsumer;
288};
289
290TEST_F(PrerequisiteModulesTests, NonModularTest) {
291 MockDirectoryCompilationDatabase CDB(TestDir, FS);
292
293 CDB.addFile("foo.h", R"cpp(
294inline void foo() {}
295 )cpp");
296
297 CDB.addFile("NonModular.cpp", R"cpp(
298#include "foo.h"
299void use() {
300 foo();
301}
302 )cpp");
303
304 ModulesBuilder Builder(CDB);
305
306 // NonModular.cpp is not related to modules. So nothing should be built.
307 auto NonModularInfo =
308 Builder.buildPrerequisiteModulesFor(getFullPath("NonModular.cpp"), FS);
309 EXPECT_TRUE(NonModularInfo);
310
311 HeaderSearchOptions HSOpts;
312 NonModularInfo->adjustHeaderSearchOptions(HSOpts);
313 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
314
315 auto Invocation =
316 buildCompilerInvocation(getInputs("NonModular.cpp", CDB), DiagConsumer);
317 EXPECT_TRUE(NonModularInfo->canReuse(*Invocation, FS.view(TestDir)));
318}
319
320TEST_F(PrerequisiteModulesTests, ModuleWithoutDepTest) {
321 MockDirectoryCompilationDatabase CDB(TestDir, FS);
322
323 CDB.addFile("foo.h", R"cpp(
324inline void foo() {}
325 )cpp");
326
327 CDB.addFile("M.cppm", R"cpp(
328module;
329#include "foo.h"
330export module M;
331 )cpp");
332
333 ModulesBuilder Builder(CDB);
334
335 auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS);
336 EXPECT_TRUE(MInfo);
337
338 // Nothing should be built since M doesn't dependent on anything.
339 HeaderSearchOptions HSOpts;
340 MInfo->adjustHeaderSearchOptions(HSOpts);
341 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
342
343 auto Invocation =
344 buildCompilerInvocation(getInputs("M.cppm", CDB), DiagConsumer);
345 EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir)));
346}
347
348TEST_F(PrerequisiteModulesTests, ObservedModuleOutsideCompilationDatabase) {
349 MockDirectoryCompilationDatabase CDB(TestDir, FS);
350 CDB.addFile("Use.cpp", R"cpp(
351import M;
352 )cpp");
353
354 SmallString<256> ModulePath(TestDir);
355 llvm::sys::path::append(ModulePath, "M.cppm");
356 std::error_code EC;
357 llvm::raw_fd_ostream OS(ModulePath, EC);
358 ASSERT_FALSE(EC);
359 OS << "export module M;";
360 OS.close();
361
362 ModulesBuilder Builder(CDB);
363 EXPECT_TRUE(Builder.observeSourcePath(ModulePath));
364 EXPECT_FALSE(Builder.observeSourcePath(ModulePath));
365 auto Info = Builder.buildPrerequisiteModulesFor(getFullPath("Use.cpp"), FS);
366
367 HeaderSearchOptions HSOpts;
368 Info->adjustHeaderSearchOptions(HSOpts);
369 EXPECT_EQ(HSOpts.PrebuiltModuleFiles.count("M"), 1u);
370}
371
372TEST_F(PrerequisiteModulesTests, ModuleWithArgumentPatch) {
373 MockDirectoryCompilationDatabase CDB(TestDir, FS);
374
375 CDB.ExtraClangFlags.push_back("-invalid-unknown-flag");
376
377 CDB.addFile("Dep.cppm", R"cpp(
378export module Dep;
379 )cpp");
380
381 CDB.addFile("M.cppm", R"cpp(
382export module M;
383import Dep;
384 )cpp");
385
386 // An invalid flag will break the module compilation and the
387 // getRequiredModules would return an empty array
388 auto ProjectModules = CDB.getProjectModules(getFullPath("M.cppm"));
389 EXPECT_TRUE(
390 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
391
392 // Set the mangler to filter out the invalid flag
393 ProjectModules->setCommandMangler([](tooling::CompileCommand &Command,
394 PathRef) {
395 auto const It = llvm::find(Command.CommandLine, "-invalid-unknown-flag");
396 Command.CommandLine.erase(It);
397 });
398
399 // And now it returns a non-empty list of required modules since the
400 // compilation succeeded
401 EXPECT_FALSE(
402 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
403}
404
405TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) {
406 MockDirectoryCompilationDatabase CDB(TestDir, FS);
407
408 CDB.addFile("foo.h", R"cpp(
409inline void foo() {}
410 )cpp");
411
412 CDB.addFile("M.cppm", R"cpp(
413module;
414#include "foo.h"
415export module M;
416 )cpp");
417
418 CDB.addFile("N.cppm", R"cpp(
419export module N;
420import :Part;
421import M;
422 )cpp");
423
424 CDB.addFile("N-part.cppm", R"cpp(
425// Different module name with filename intentionally.
426export module N:Part;
427 )cpp");
428
429 ModulesBuilder Builder(CDB);
430
431 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
432 EXPECT_TRUE(NInfo);
433
434 ParseInputs NInput = getInputs("N.cppm", CDB);
435 std::unique_ptr<CompilerInvocation> Invocation =
436 buildCompilerInvocation(NInput, DiagConsumer);
437 // Test that `PrerequisiteModules::canReuse` works basically.
438 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
439
440 {
441 // Check that
442 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
443 // can appending HeaderSearchOptions correctly.
444 HeaderSearchOptions HSOpts;
445 NInfo->adjustHeaderSearchOptions(HSOpts);
446
447 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
448 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N:Part"));
449 }
450
451 {
452 // Check that
453 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
454 // can replace HeaderSearchOptions correctly.
455 HeaderSearchOptions HSOpts;
456 HSOpts.PrebuiltModuleFiles["M"] = "incorrect_path";
457 HSOpts.PrebuiltModuleFiles["N:Part"] = "incorrect_path";
458 NInfo->adjustHeaderSearchOptions(HSOpts);
459
460 EXPECT_TRUE(StringRef(HSOpts.PrebuiltModuleFiles["M"]).ends_with(".pcm"));
461 EXPECT_TRUE(
462 StringRef(HSOpts.PrebuiltModuleFiles["N:Part"]).ends_with(".pcm"));
463 }
464}
465
466TEST_F(PrerequisiteModulesTests, ReusabilityTest) {
467 MockDirectoryCompilationDatabase CDB(TestDir, FS);
468
469 CDB.addFile("foo.h", R"cpp(
470inline void foo() {}
471 )cpp");
472
473 CDB.addFile("M.cppm", R"cpp(
474module;
475#include "foo.h"
476export module M;
477 )cpp");
478
479 CDB.addFile("N.cppm", R"cpp(
480export module N;
481import :Part;
482import M;
483 )cpp");
484
485 CDB.addFile("N-part.cppm", R"cpp(
486// Different module name with filename intentionally.
487export module N:Part;
488 )cpp");
489
490 ModulesBuilder Builder(CDB);
491
492 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
493 EXPECT_TRUE(NInfo);
494 EXPECT_TRUE(NInfo);
495
496 ParseInputs NInput = getInputs("N.cppm", CDB);
497 std::unique_ptr<CompilerInvocation> Invocation =
498 buildCompilerInvocation(NInput, DiagConsumer);
499 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
500
501 // Test that we can still reuse the NInfo after we touch a unrelated file.
502 {
503 CDB.addFile("L.cppm", R"cpp(
504module;
505#include "foo.h"
506export module L;
507export int ll = 43;
508 )cpp");
509 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
510
511 CDB.addFile("bar.h", R"cpp(
512inline void bar() {}
513inline void bar(int) {}
514 )cpp");
515 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
516 }
517
518 // Test that we can't reuse the NInfo after we touch a related file.
519 {
520 CDB.addFile("M.cppm", R"cpp(
521module;
522#include "foo.h"
523export module M;
524export int mm = 44;
525 )cpp");
526 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
527
528 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
529 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
530
531 CDB.addFile("foo.h", R"cpp(
532inline void foo() {}
533inline void foo(int) {}
534 )cpp");
535 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
536
537 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
538 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
539 }
540
541 CDB.addFile("N-part.cppm", R"cpp(
542export module N:Part;
543// Intentioned to make it uncompilable.
544export int NPart = 4LIdjwldijaw
545 )cpp");
546 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
547 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
548 EXPECT_TRUE(NInfo);
549 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
550
551 CDB.addFile("N-part.cppm", R"cpp(
552export module N:Part;
553export int NPart = 43;
554 )cpp");
555 EXPECT_TRUE(NInfo);
556 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
557 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
558 EXPECT_TRUE(NInfo);
559 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
560
561 // Test that if we changed the modification time of the file, the module files
562 // info is still reusable if its content doesn't change.
563 CDB.addFile("N-part.cppm", R"cpp(
564export module N:Part;
565export int NPart = 43;
566 )cpp");
567 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
568
569 CDB.addFile("N.cppm", R"cpp(
570export module N;
571import :Part;
572import M;
573
574export int nn = 43;
575 )cpp");
576 // NInfo should be reusable after we change its content.
577 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
578}
579
580TEST_F(PrerequisiteModulesTests, CanReuseWithTransitiveNamedModuleImports) {
581 MockDirectoryCompilationDatabase CDB(TestDir, FS);
582
583 CDB.addFile("N.cppm", R"cpp(
584export module N;
585export inline constexpr int n = 1;
586 )cpp");
587
588 CDB.addFile("M.cppm", R"cpp(
589export module M;
590import N;
591export inline constexpr int m = n + 1;
592 )cpp");
593
594 CDB.addFile("A.cppm", R"cpp(
595export module A;
596import M;
597export inline constexpr int a = m + 1;
598 )cpp");
599
600 CDB.addFile("Use.cpp", R"cpp(
601import A;
602int use() { return a; }
603 )cpp");
604
605 ModulesBuilder Builder(CDB);
606
607 auto UseInfo =
608 Builder.buildPrerequisiteModulesFor(getFullPath("Use.cpp"), FS);
609 ASSERT_TRUE(UseInfo);
610
611 HeaderSearchOptions HSOpts;
612 UseInfo->adjustHeaderSearchOptions(HSOpts);
613 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("A"));
614 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
615 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N"));
616
617 auto Invocation =
618 buildCompilerInvocation(getInputs("Use.cpp", CDB), DiagConsumer);
619 ASSERT_TRUE(Invocation);
620
621 EXPECT_TRUE(UseInfo->canReuse(*Invocation, FS.view(TestDir)));
622}
623
624// An End-to-End test for modules.
625TEST_F(PrerequisiteModulesTests, ParsedASTTest) {
626 MockDirectoryCompilationDatabase CDB(TestDir, FS);
627
628 CDB.addFile("A.cppm", R"cpp(
629export module A;
630export void printA();
631 )cpp");
632
633 CDB.addFile("Use.cpp", R"cpp(
634import A;
635)cpp");
636
637 ModulesBuilder Builder(CDB);
638
639 ParseInputs Use = getInputs("Use.cpp", CDB);
640 Use.ModulesManager = &Builder;
641
642 std::unique_ptr<CompilerInvocation> CI =
643 buildCompilerInvocation(Use, DiagConsumer);
644 EXPECT_TRUE(CI);
645
646 auto Preamble =
647 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
648 /*Callback=*/nullptr);
649 EXPECT_TRUE(Preamble);
650 EXPECT_TRUE(Preamble->RequiredModules);
651
652 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Use, std::move(CI), {},
653 Preamble);
654 EXPECT_TRUE(AST);
655
656 const NamedDecl &D = findDecl(*AST, "printA");
657 EXPECT_TRUE(D.isFromASTFile());
658}
659
660TEST_F(PrerequisiteModulesTests, LocateImportedModule) {
661 MockDirectoryCompilationDatabase CDB(TestDir, FS);
662
663 Annotations Dep(R"cpp(
664export $decl[[module]] dep.one.two;
665)cpp");
666 CDB.addFile("Dep.cppm", Dep.code());
667
668 Annotations Part(R"cpp(
669export $decl[[module]] M:part.one;
670)cpp");
671 CDB.addFile("M-part.cppm", Part.code());
672
673 Annotations Use(R"cpp(
674export module M;
675import $dep0^dep.$dep1^one.$dep2^two;
676import :$part0^part.$part1^one;
677)cpp");
678 CDB.addFile("M.cppm", Use.code());
679
680 ModulesBuilder Builder(CDB);
681 auto Inputs = getInputs("M.cppm", CDB);
682 Inputs.ModulesManager = &Builder;
683 Inputs.Opts.SkipPreambleBuild = true;
684
685 auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
686 ASSERT_TRUE(CI);
687 auto Preamble =
688 buildPreamble(getFullPath("M.cppm"), *CI, Inputs, /*InMemory=*/true,
689 /*Callback=*/nullptr);
690 ASSERT_TRUE(Preamble);
691
692 auto AST = ParsedAST::build(getFullPath("M.cppm"), Inputs, std::move(CI), {},
693 Preamble);
694 ASSERT_TRUE(AST);
695 ASSERT_TRUE(AST->getDiagnostics().empty());
696
697 auto Check = [&](llvm::StringRef Point, llvm::StringRef Name,
698 llvm::StringRef File, Range TargetRange) {
699 auto Results = locateSymbolAt(*AST, Use.point(Point));
700 ASSERT_THAT(Results, testing::SizeIs(1));
701 EXPECT_EQ(Results.front().Name, Name);
702 Location Target{
703 URIForFile::canonicalize(getFullPath(File), getFullPath("M.cppm")),
704 TargetRange};
705 EXPECT_EQ(Results.front().PreferredDeclaration, Target);
706 EXPECT_EQ(Results.front().Definition, Target);
707 };
708
709 for (llvm::StringRef Point : {"dep0", "dep1", "dep2"})
710 Check(Point, "dep.one.two", "Dep.cppm", Dep.range("decl"));
711 for (llvm::StringRef Point : {"part0", "part1"})
712 Check(Point, "M:part.one", "M-part.cppm", Part.range("decl"));
713}
714
715// An end to end test for code complete in modules
716TEST_F(PrerequisiteModulesTests, CodeCompleteTest) {
717 MockDirectoryCompilationDatabase CDB(TestDir, FS);
718
719 CDB.addFile("A.cppm", R"cpp(
720export module A;
721export void printA();
722 )cpp");
723
724 llvm::StringLiteral UserContents = R"cpp(
725import A;
726void func() {
727 print^
728}
729)cpp";
730
731 CDB.addFile("Use.cpp", UserContents);
732 Annotations Test(UserContents);
733
734 ModulesBuilder Builder(CDB);
735
736 ParseInputs Use = getInputs("Use.cpp", CDB);
737 Use.ModulesManager = &Builder;
738
739 std::unique_ptr<CompilerInvocation> CI =
740 buildCompilerInvocation(Use, DiagConsumer);
741 EXPECT_TRUE(CI);
742
743 auto Preamble =
744 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
745 /*Callback=*/nullptr);
746 EXPECT_TRUE(Preamble);
747 EXPECT_TRUE(Preamble->RequiredModules);
748
749 auto Result = codeComplete(getFullPath("Use.cpp"), Test.point(),
750 Preamble.get(), Use, {});
751 EXPECT_FALSE(Result.Completions.empty());
752 EXPECT_EQ(Result.Completions[0].Name, "printA");
753}
754
755TEST_F(PrerequisiteModulesTests, SignatureHelpTest) {
756 MockDirectoryCompilationDatabase CDB(TestDir, FS);
757
758 CDB.addFile("A.cppm", R"cpp(
759export module A;
760export void printA(int a);
761 )cpp");
762
763 llvm::StringLiteral UserContents = R"cpp(
764import A;
765void func() {
766 printA(^);
767}
768)cpp";
769
770 CDB.addFile("Use.cpp", UserContents);
771 Annotations Test(UserContents);
772
773 ModulesBuilder Builder(CDB);
774
775 ParseInputs Use = getInputs("Use.cpp", CDB);
776 Use.ModulesManager = &Builder;
777
778 std::unique_ptr<CompilerInvocation> CI =
779 buildCompilerInvocation(Use, DiagConsumer);
780 EXPECT_TRUE(CI);
781
782 auto Preamble =
783 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
784 /*Callback=*/nullptr);
785 EXPECT_TRUE(Preamble);
786 EXPECT_TRUE(Preamble->RequiredModules);
787
788 auto Result = signatureHelp(getFullPath("Use.cpp"), Test.point(), *Preamble,
790 EXPECT_FALSE(Result.signatures.empty());
791 EXPECT_EQ(Result.signatures[0].label, "printA(int a) -> void");
792 EXPECT_EQ(Result.signatures[0].parameters[0].labelString, "int a");
793}
794
795TEST_F(PrerequisiteModulesTests, ReusablePrerequisiteModulesTest) {
796 MockDirectoryCompilationDatabase CDB(TestDir, FS);
797
798 CDB.addFile("M.cppm", R"cpp(
799export module M;
800export int M = 43;
801 )cpp");
802 CDB.addFile("A.cppm", R"cpp(
803export module A;
804import M;
805export int A = 43 + M;
806 )cpp");
807 CDB.addFile("B.cppm", R"cpp(
808export module B;
809import M;
810export int B = 44 + M;
811 )cpp");
812
813 ModulesBuilder Builder(CDB);
814
815 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
816 EXPECT_TRUE(AInfo);
817 auto BInfo = Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
818 EXPECT_TRUE(BInfo);
819 HeaderSearchOptions HSOptsA(TestDir);
820 HeaderSearchOptions HSOptsB(TestDir);
821 AInfo->adjustHeaderSearchOptions(HSOptsA);
822 BInfo->adjustHeaderSearchOptions(HSOptsB);
823
824 EXPECT_FALSE(HSOptsA.PrebuiltModuleFiles.empty());
825 EXPECT_FALSE(HSOptsB.PrebuiltModuleFiles.empty());
826
827 // Check that we're reusing the module files.
828 EXPECT_EQ(HSOptsA.PrebuiltModuleFiles, HSOptsB.PrebuiltModuleFiles);
829
830 // Update M.cppm to check if the modules builder can update correctly.
831 CDB.addFile("M.cppm", R"cpp(
832export module M;
833export constexpr int M = 43;
834 )cpp");
835
836 ParseInputs AUse = getInputs("A.cppm", CDB);
837 AUse.ModulesManager = &Builder;
838 std::unique_ptr<CompilerInvocation> AInvocation =
839 buildCompilerInvocation(AUse, DiagConsumer);
840 EXPECT_FALSE(AInfo->canReuse(*AInvocation, FS.view(TestDir)));
841
842 ParseInputs BUse = getInputs("B.cppm", CDB);
843 AUse.ModulesManager = &Builder;
844 std::unique_ptr<CompilerInvocation> BInvocation =
845 buildCompilerInvocation(BUse, DiagConsumer);
846 EXPECT_FALSE(BInfo->canReuse(*BInvocation, FS.view(TestDir)));
847
848 auto NewAInfo =
849 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
850 auto NewBInfo =
851 Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
852 EXPECT_TRUE(NewAInfo);
853 EXPECT_TRUE(NewBInfo);
854 HeaderSearchOptions NewHSOptsA(TestDir);
855 HeaderSearchOptions NewHSOptsB(TestDir);
856 NewAInfo->adjustHeaderSearchOptions(NewHSOptsA);
857 NewBInfo->adjustHeaderSearchOptions(NewHSOptsB);
858
859 EXPECT_FALSE(NewHSOptsA.PrebuiltModuleFiles.empty());
860 EXPECT_FALSE(NewHSOptsB.PrebuiltModuleFiles.empty());
861
862 EXPECT_EQ(NewHSOptsA.PrebuiltModuleFiles, NewHSOptsB.PrebuiltModuleFiles);
863 // Persistent cache keeps the published BMI path stable, so verify the new
864 // module graph by reuse semantics instead of expecting a different path.
865 EXPECT_TRUE(NewAInfo->canReuse(*AInvocation, FS.view(TestDir)));
866 EXPECT_TRUE(NewBInfo->canReuse(*BInvocation, FS.view(TestDir)));
867}
868
869TEST_F(PrerequisiteModulesTests, ScanningCacheTest) {
870 MockDirectoryCompilationDatabase CDB(TestDir, FS);
871
872 CDB.addFile("M.cppm", R"cpp(
873export module M;
874 )cpp");
875 CDB.addFile("A.cppm", R"cpp(
876export module A;
877import M;
878 )cpp");
879 CDB.addFile("B.cppm", R"cpp(
880export module B;
881import M;
882 )cpp");
883
884 ModulesBuilder Builder(CDB);
885
886 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
887 Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
888 EXPECT_EQ(CDB.getGlobalScanningCount(), 1u);
889}
890
891// Test that canReuse detects changes to headers included in module units.
892// This verifies that the ASTReader correctly tracks header file dependencies
893// in BMI files and that IsModuleFileUpToDate correctly validates them.
894TEST_F(PrerequisiteModulesTests, CanReuseWithHeadersInModuleUnit) {
895 MockDirectoryCompilationDatabase CDB(TestDir, FS);
896
897 // Create a header file that will be included in a module unit
898 CDB.addFile("header1.h", R"cpp(
899inline int getValue() { return 42; }
900 )cpp");
901
902 // Module M includes header1.h in the global module fragment
903 CDB.addFile("M.cppm", R"cpp(
904module;
905#include "header1.h"
906export module M;
907export int m_value = getValue();
908 )cpp");
909
910 // Module N imports M (similar structure to ReusabilityTest)
911 CDB.addFile("N.cppm", R"cpp(
912export module N;
913import :Part;
914import M;
915 )cpp");
916
917 // Add a module partition (similar to ReusabilityTest)
918 CDB.addFile("N-part.cppm", R"cpp(
919export module N:Part;
920 )cpp");
921
922 ModulesBuilder Builder(CDB);
923
924 // Build prerequisite modules for N (which depends on M)
925 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
926 ASSERT_TRUE(NInfo);
927
928 ParseInputs NInput = getInputs("N.cppm", CDB);
929 std::unique_ptr<CompilerInvocation> Invocation =
930 buildCompilerInvocation(NInput, DiagConsumer);
931
932 // Initially, canReuse should return true
933 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
934
935 // Test 1: Modify header1.h (included by M)
936 // canReuse should detect this change since M's BMI records header1.h as input
937 CDB.addFile("header1.h", R"cpp(
938inline int getValue() { return 43; }
939 )cpp");
940 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
941
942 // Rebuild and verify canReuse returns true again
943 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
944 ASSERT_TRUE(NInfo);
945 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
946
947 // Test 2: Modify the module source file itself
948 CDB.addFile("M.cppm", R"cpp(
949module;
950#include "header1.h"
951export module M;
952export int m_value = getValue();
953export int m_new_value = 10;
954 )cpp");
955 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
956
957 // Rebuild after module source change
958 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
959 ASSERT_TRUE(NInfo);
960 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
961}
962
963TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileTest) {
964 MockDirectoryCompilationDatabase CDB(TestDir, FS);
965
966 CDB.addFile("M.cppm", R"cpp(
967export module M;
968 )cpp");
969
970 CDB.addFile("U.cpp", R"cpp(
971import M;
972 )cpp");
973
974 // Use ModulesBuilder to produce the prebuilt module file.
975 ModulesBuilder Builder(CDB);
976 auto ModuleInfo =
977 Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
978 HeaderSearchOptions HS(TestDir);
979 ModuleInfo->adjustHeaderSearchOptions(HS);
980
981 CDB.ExtraClangFlags.push_back("-fmodule-file=M=" +
982 HS.PrebuiltModuleFiles["M"]);
983 ModulesBuilder Builder2(CDB);
984 auto ModuleInfo2 =
985 Builder2.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
986 HeaderSearchOptions HS2(TestDir);
987 ModuleInfo2->adjustHeaderSearchOptions(HS2);
988
989 EXPECT_EQ(HS.PrebuiltModuleFiles, HS2.PrebuiltModuleFiles);
990}
991
992// Test that prebuilt module files with relative paths are correctly resolved.
993// This tests the fix for the issue where clangd couldn't find BMI files when
994// the compilation database contained relative paths in -fmodule-file=
995// arguments.
996TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileWithRelativePath) {
997 MockDirectoryCompilationDatabase CDB(TestDir, FS);
998
999 CDB.addFile("M.cppm", R"cpp(
1000export module M;
1001export int m_value = 42;
1002 )cpp");
1003
1004 CDB.addFile("U.cpp", R"cpp(
1005import M;
1006int use() { return m_value; }
1007 )cpp");
1008
1009 // Step 1: Build the module file using ModulesBuilder
1010 ModulesBuilder Builder(CDB);
1011 auto ModuleInfo =
1012 Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
1013 ASSERT_TRUE(ModuleInfo);
1014
1015 HeaderSearchOptions HS(TestDir);
1016 ModuleInfo->adjustHeaderSearchOptions(HS);
1017 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1018
1019 // Get the absolute path of the built module file
1020 std::string OriginalBMPath = HS.PrebuiltModuleFiles["M"];
1021 ASSERT_TRUE(llvm::sys::path::is_absolute(OriginalBMPath));
1022 ASSERT_TRUE(llvm::sys::fs::exists(OriginalBMPath));
1023
1024 // Step 2: Create a subdirectory in TestDir and copy the BMI there
1025 SmallString<256> BMSubDir(TestDir);
1026 llvm::sys::path::append(BMSubDir, "prebuilt_modules");
1027 ASSERT_FALSE(llvm::sys::fs::create_directories(BMSubDir));
1028
1029 SmallString<256> NewBMPath(BMSubDir);
1030 llvm::sys::path::append(NewBMPath, "M.pcm");
1031
1032 // Copy the BMI file to the new location
1033 ASSERT_FALSE(llvm::sys::fs::copy_file(OriginalBMPath, NewBMPath));
1034 ASSERT_TRUE(llvm::sys::fs::exists(NewBMPath));
1035
1036 // Step 3: Create a relative path from the new absolute path
1037 std::string RelativeBMPath =
1038 llvm::StringRef(NewBMPath).drop_front(TestDir.size() + 1).str();
1039 ASSERT_FALSE(RelativeBMPath.empty());
1040 ASSERT_TRUE(llvm::sys::path::is_relative(RelativeBMPath));
1041
1042 // Step 4: Create a new CDB with relative path in -fmodule-file=
1043 MockDirectoryCompilationDatabase CDBWithRelativePath(TestDir, FS);
1044
1045 CDBWithRelativePath.addFile("M.cppm", R"cpp(
1046export module M;
1047export int m_value = 42;
1048 )cpp");
1049
1050 CDBWithRelativePath.addFile("U.cpp", R"cpp(
1051import M;
1052int use() { return m_value; }
1053 )cpp");
1054
1055 // Use relative path in -fmodule-file= argument
1056 CDBWithRelativePath.ExtraClangFlags.push_back("-fmodule-file=M=" +
1057 RelativeBMPath);
1058
1059 // Step 5: Verify that clangd can find and reuse the prebuilt module file
1060 ModulesBuilder BuilderWithRelativePath(CDBWithRelativePath);
1061 auto ModuleInfo2 = BuilderWithRelativePath.buildPrerequisiteModulesFor(
1062 getFullPath("U.cpp"), FS);
1063 ASSERT_TRUE(ModuleInfo2);
1064
1065 HeaderSearchOptions HS2(TestDir);
1066 ModuleInfo2->adjustHeaderSearchOptions(HS2);
1067
1068 // The module file should be found and the paths should match
1069 ASSERT_EQ(HS2.PrebuiltModuleFiles.count("M"), 1u);
1070 EXPECT_EQ(HS2.PrebuiltModuleFiles["M"], std::string(NewBMPath))
1071 << "Expected absolute path: " << NewBMPath
1072 << "\nGot: " << HS2.PrebuiltModuleFiles["M"]
1073 << "\nRelative path used: " << RelativeBMPath;
1074}
1075
1076TEST_F(PrerequisiteModulesTests,
1077 UniqueModuleNameStateResolvedFromCompileCommands) {
1078 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1079
1080 SmallString<256> MPcm(TestDir);
1081 llvm::sys::path::append(MPcm, "build", "M.pcm");
1082
1083 CDB.addFile("M.cppm", R"cpp(
1084export module M;
1085export int value = 1;
1086 )cpp",
1087 {"--precompile", "-o", std::string(MPcm)});
1088 CDB.addFile("A.cpp", R"cpp(
1089import M;
1090int useA() { return value; }
1091 )cpp",
1092 {"-fmodule-file=M=" + std::string(MPcm)});
1093 CDB.addFile("B.cpp", R"cpp(
1094import M;
1095int useB() { return value; }
1096 )cpp",
1097 {"-fmodule-file=M=" + std::string(MPcm)});
1098
1099 auto ProjectModules = CDB.getProjectModules(getFullPath("A.cpp"));
1100 ASSERT_TRUE(ProjectModules);
1101
1102 EXPECT_EQ(ProjectModules->getModuleNameState("M"),
1104}
1105
1106TEST_F(PrerequisiteModulesTests,
1107 DuplicateModuleNamesResolvedFromCompileCommands) {
1108 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1109
1110 SmallString<256> APcm(TestDir);
1111 llvm::sys::path::append(APcm, "build", "a", "M.pcm");
1112 SmallString<256> BPcm(TestDir);
1113 llvm::sys::path::append(BPcm, "build", "b", "M.pcm");
1114
1115 CDB.addFile("a/M.cppm", R"cpp(
1116export module M;
1117export int onlyA = 1;
1118 )cpp",
1119 {"--precompile", "-o", std::string(APcm)});
1120 CDB.addFile("b/M.cppm", R"cpp(
1121export module M;
1122export int onlyB = 2;
1123 )cpp",
1124 {"--precompile", "-o", std::string(BPcm)});
1125 CDB.addFile("a/Use.cpp", R"cpp(
1126import M;
1127int useA() { return onlyA; }
1128 )cpp",
1129 {"-fmodule-file=M=" + std::string(APcm)});
1130 CDB.addFile("b/Use.cpp", R"cpp(
1131import M;
1132int useB() { return onlyB; }
1133 )cpp",
1134 {"-fmodule-file=M=" + std::string(BPcm)});
1135
1136 auto ProjectModules = CDB.getProjectModules(getFullPath("a/Use.cpp"));
1137 ASSERT_TRUE(ProjectModules);
1138 EXPECT_EQ(ProjectModules->getModuleNameState("M"),
1140
1141 EXPECT_EQ(
1142 ProjectModules->getSourceForModuleName("M", getFullPath("a/Use.cpp")),
1143 getFullPath("a/M.cppm"));
1144 EXPECT_EQ(
1145 ProjectModules->getSourceForModuleName("M", getFullPath("b/Use.cpp")),
1146 getFullPath("b/M.cppm"));
1147}
1148
1149TEST_F(PrerequisiteModulesTests,
1150 DuplicateModuleNamesResolvedFromResponseFiles) {
1151 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1152
1153 SmallString<256> APcm(TestDir);
1154 llvm::sys::path::append(APcm, "build", "a", "M.pcm");
1155 SmallString<256> BPcm(TestDir);
1156 llvm::sys::path::append(BPcm, "build", "b", "M.pcm");
1157
1158 SmallString<256> RspDir(TestDir);
1159 llvm::sys::path::append(RspDir, "build", "rsp");
1160 ASSERT_FALSE(llvm::sys::fs::create_directories(RspDir));
1161
1162 SmallString<256> AMRsp(RspDir);
1163 llvm::sys::path::append(AMRsp, "a-m.rsp");
1164 {
1165 std::error_code EC;
1166 llvm::raw_fd_ostream OS(AMRsp, EC);
1167 ASSERT_FALSE(EC);
1168 OS << "-x c++-module -fmodule-output=" << APcm;
1169 OS.close();
1170 }
1171
1172 SmallString<256> BMRsp(RspDir);
1173 llvm::sys::path::append(BMRsp, "b-m.rsp");
1174 {
1175 std::error_code EC;
1176 llvm::raw_fd_ostream OS(BMRsp, EC);
1177 ASSERT_FALSE(EC);
1178 OS << "-x c++-module -fmodule-output=" << BPcm;
1179 OS.close();
1180 }
1181
1182 SmallString<256> AUseRsp(RspDir);
1183 llvm::sys::path::append(AUseRsp, "a-use.rsp");
1184 {
1185 std::error_code EC;
1186 llvm::raw_fd_ostream OS(AUseRsp, EC);
1187 ASSERT_FALSE(EC);
1188 OS << "-fmodule-file=M=" << APcm;
1189 OS.close();
1190 }
1191
1192 SmallString<256> BUseRsp(RspDir);
1193 llvm::sys::path::append(BUseRsp, "b-use.rsp");
1194 {
1195 std::error_code EC;
1196 llvm::raw_fd_ostream OS(BUseRsp, EC);
1197 ASSERT_FALSE(EC);
1198 OS << "-fmodule-file=M=" << BPcm;
1199 OS.close();
1200 }
1201
1202 CDB.addFile("a/M.cppm", R"cpp(
1203export module M;
1204export int onlyA = 1;
1205 )cpp",
1206 {"@" + std::string(AMRsp)});
1207 CDB.addFile("b/M.cppm", R"cpp(
1208export module M;
1209export int onlyB = 2;
1210 )cpp",
1211 {"@" + std::string(BMRsp)});
1212 CDB.addFile("a/Use.cpp", R"cpp(
1213import M;
1214int useA() { return onlyA; }
1215 )cpp",
1216 {"@" + std::string(AUseRsp)});
1217 CDB.addFile("b/Use.cpp", R"cpp(
1218import M;
1219int useB() { return onlyB; }
1220 )cpp",
1221 {"@" + std::string(BUseRsp)});
1222
1223 auto ProjectModules = CDB.getProjectModules(getFullPath("a/Use.cpp"));
1224 ASSERT_TRUE(ProjectModules);
1225 EXPECT_EQ(ProjectModules->getModuleNameState("M"),
1227
1228 EXPECT_EQ(
1229 ProjectModules->getSourceForModuleName("M", getFullPath("a/Use.cpp")),
1230 getFullPath("a/M.cppm"));
1231 EXPECT_EQ(
1232 ProjectModules->getSourceForModuleName("M", getFullPath("b/Use.cpp")),
1233 getFullPath("b/M.cppm"));
1234}
1235
1236TEST_F(PrerequisiteModulesTests, DuplicateModuleNamesKeepSeparateBMICache) {
1237 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1238
1239 SmallString<256> APcm(TestDir);
1240 llvm::sys::path::append(APcm, "build", "a", "M.pcm");
1241 SmallString<256> BPcm(TestDir);
1242 llvm::sys::path::append(BPcm, "build", "b", "M.pcm");
1243
1244 CDB.addFile("a/M.cppm", R"cpp(
1245export module M;
1246export int onlyA = 1;
1247 )cpp",
1248 {"--precompile", "-o", std::string(APcm)});
1249 CDB.addFile("b/M.cppm", R"cpp(
1250export module M;
1251export int onlyB = 2;
1252 )cpp",
1253 {"--precompile", "-o", std::string(BPcm)});
1254 CDB.addFile("a/Use.cpp", R"cpp(
1255import M;
1256int useA() { return onlyA; }
1257 )cpp",
1258 {"-fmodule-file=M=" + std::string(APcm)});
1259 CDB.addFile("b/Use.cpp", R"cpp(
1260import M;
1261int useB() { return onlyB; }
1262 )cpp",
1263 {"-fmodule-file=M=" + std::string(BPcm)});
1264
1265 ModulesBuilder Builder(CDB);
1266
1267 auto AInfo =
1268 Builder.buildPrerequisiteModulesFor(getFullPath("a/Use.cpp"), FS);
1269 auto BInfo =
1270 Builder.buildPrerequisiteModulesFor(getFullPath("b/Use.cpp"), FS);
1271 ASSERT_TRUE(AInfo);
1272 ASSERT_TRUE(BInfo);
1273
1274 HeaderSearchOptions HSA(TestDir);
1275 HeaderSearchOptions HSB(TestDir);
1276 AInfo->adjustHeaderSearchOptions(HSA);
1277 BInfo->adjustHeaderSearchOptions(HSB);
1278 ASSERT_EQ(HSA.PrebuiltModuleFiles.count("M"), 1u);
1279 ASSERT_EQ(HSB.PrebuiltModuleFiles.count("M"), 1u);
1280 EXPECT_NE(HSA.PrebuiltModuleFiles["M"], HSB.PrebuiltModuleFiles["M"]);
1281
1282 auto UseA = getInputs("a/Use.cpp", CDB);
1283 UseA.ModulesManager = &Builder;
1284 auto CIA = buildCompilerInvocation(UseA, DiagConsumer);
1285 ASSERT_TRUE(CIA);
1286 auto PreambleA = buildPreamble(getFullPath("a/Use.cpp"), *CIA, UseA,
1287 /*InMemory=*/true, /*Callback=*/nullptr);
1288 ASSERT_TRUE(PreambleA);
1289 auto ASTA = ParsedAST::build(getFullPath("a/Use.cpp"), UseA, std::move(CIA),
1290 {}, PreambleA);
1291 ASSERT_TRUE(ASTA);
1292 EXPECT_TRUE(findDecl(*ASTA, "onlyA").isFromASTFile());
1293
1294 auto UseB = getInputs("b/Use.cpp", CDB);
1295 UseB.ModulesManager = &Builder;
1296 auto CIB = buildCompilerInvocation(UseB, DiagConsumer);
1297 ASSERT_TRUE(CIB);
1298 auto PreambleB = buildPreamble(getFullPath("b/Use.cpp"), *CIB, UseB,
1299 /*InMemory=*/true, /*Callback=*/nullptr);
1300 ASSERT_TRUE(PreambleB);
1301 auto ASTB = ParsedAST::build(getFullPath("b/Use.cpp"), UseB, std::move(CIB),
1302 {}, PreambleB);
1303 ASSERT_TRUE(ASTB);
1304 EXPECT_TRUE(findDecl(*ASTB, "onlyB").isFromASTFile());
1305}
1306
1307TEST_F(PrerequisiteModulesTests, PersistentModuleCacheReusedAcrossBuilders) {
1308 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1309
1310 CDB.addFile("M.cppm", R"cpp(
1311export module M;
1312export int MValue = 43;
1313 )cpp");
1314 CDB.addFile("A.cppm", R"cpp(
1315export module A;
1316import M;
1317export int AValue = MValue;
1318 )cpp");
1319
1320 std::string FirstPCMPath;
1321 {
1322 ModulesBuilder Builder(CDB);
1323 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1324 ASSERT_TRUE(AInfo);
1325 HeaderSearchOptions HS(TestDir);
1326 AInfo->adjustHeaderSearchOptions(HS);
1327 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1328 FirstPCMPath = HS.PrebuiltModuleFiles["M"];
1329 EXPECT_TRUE(llvm::sys::fs::exists(FirstPCMPath));
1330 EXPECT_TRUE(StringRef(FirstPCMPath).contains(".cache/clangd/modules"));
1331 }
1332
1333 EXPECT_FALSE(llvm::sys::fs::exists(FirstPCMPath));
1334
1335 // A fresh builder should reuse the persistent BMI published by the first one
1336 // instead of rebuilding its stable cache entry.
1337 ModulesBuilder Builder(CDB);
1338 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1339 ASSERT_TRUE(AInfo);
1340 HeaderSearchOptions HS(TestDir);
1341 AInfo->adjustHeaderSearchOptions(HS);
1342 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1343 EXPECT_TRUE(llvm::sys::fs::exists(HS.PrebuiltModuleFiles["M"]));
1344 EXPECT_TRUE(StringRef(HS.PrebuiltModuleFiles["M"]).contains("M-"));
1345
1346 ParseInputs AUse = getInputs("A.cppm", CDB);
1347 AUse.ModulesManager = &Builder;
1348 auto Invocation = buildCompilerInvocation(AUse, DiagConsumer);
1349 ASSERT_TRUE(Invocation);
1350 EXPECT_TRUE(AInfo->canReuse(*Invocation, FS.view(TestDir)));
1351}
1352
1353TEST_F(PrerequisiteModulesTests,
1354 PersistentModuleCacheRebuildsAfterDeletingStalePCM) {
1355 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1356
1357 CDB.addFile("M.cppm", R"cpp(
1358export module M;
1359export int MValue = 43;
1360 )cpp");
1361 CDB.addFile("A.cppm", R"cpp(
1362export module A;
1363import M;
1364export int AValue = MValue;
1365 )cpp");
1366
1367 ModulesBuilder Builder(CDB);
1368 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1369 ASSERT_TRUE(AInfo);
1370 HeaderSearchOptions HS(TestDir);
1371 AInfo->adjustHeaderSearchOptions(HS);
1372 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1373 std::string PCMPath = HS.PrebuiltModuleFiles["M"];
1374
1375 std::error_code EC;
1376 llvm::raw_fd_ostream OS(PCMPath, EC);
1377 ASSERT_FALSE(EC);
1378 OS << "broken";
1379 OS.close();
1380
1381 // Corrupt the handed-out BMI and ensure clangd rebuilds a usable replacement.
1382 auto NewAInfo =
1383 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1384 ASSERT_TRUE(NewAInfo);
1385 HeaderSearchOptions NewHS(TestDir);
1386 NewAInfo->adjustHeaderSearchOptions(NewHS);
1387 ASSERT_EQ(NewHS.PrebuiltModuleFiles.count("M"), 1u);
1388 EXPECT_TRUE(llvm::sys::fs::exists(NewHS.PrebuiltModuleFiles["M"]));
1389 EXPECT_TRUE(StringRef(NewHS.PrebuiltModuleFiles["M"]).contains("M-"));
1390
1391 ParseInputs AUse = getInputs("A.cppm", CDB);
1392 AUse.ModulesManager = &Builder;
1393 auto Invocation = buildCompilerInvocation(AUse, DiagConsumer);
1394 ASSERT_TRUE(Invocation);
1395 EXPECT_TRUE(NewAInfo->canReuse(*Invocation, FS.view(TestDir)));
1396}
1397
1398TEST_F(PrerequisiteModulesTests, PersistentModuleCacheCreatesSourceHashLock) {
1399 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1400
1401 CDB.addFile("M.cppm", R"cpp(
1402export module M;
1403export int MValue = 43;
1404 )cpp");
1405 CDB.addFile("A.cppm", R"cpp(
1406export module A;
1407import M;
1408export int AValue = MValue;
1409 )cpp");
1410
1411 ModulesBuilder Builder(CDB);
1412 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1413 ASSERT_TRUE(AInfo);
1414
1415 HeaderSearchOptions HS(TestDir);
1416 AInfo->adjustHeaderSearchOptions(HS);
1417 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1418
1419 llvm::SmallString<256> PCMPath(HS.PrebuiltModuleFiles["M"]);
1420 llvm::sys::path::remove_filename(PCMPath);
1421 llvm::SmallString<256> SourceHashDir(PCMPath);
1422 llvm::sys::path::remove_filename(SourceHashDir);
1423 llvm::SmallString<256> CacheRoot(SourceHashDir);
1424 llvm::sys::path::remove_filename(CacheRoot);
1425
1426 // Locks live next to the persistent cache and are keyed by source-hash so
1427 // builders publishing the same module unit serialize with each other.
1428 llvm::StringRef SourceDirectoryName =
1429 llvm::sys::path::filename(SourceHashDir);
1430 // Split from the right because the readable basename may also contain '-'.
1431 llvm::StringRef SourceHash = SourceDirectoryName.rsplit('-').second;
1432 llvm::SmallString<256> LockPath(CacheRoot);
1433 llvm::sys::path::append(LockPath, ".locks", SourceHash);
1434
1435 EXPECT_TRUE(llvm::sys::fs::exists(LockPath));
1436}
1437
1438TEST_F(PrerequisiteModulesTests,
1439 PersistentModuleCacheGCRemovesOldStablePublishedModule) {
1440 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1441
1442 CDB.addFile("M.cppm", R"cpp(
1443export module M;
1444export int MValue = 43;
1445 )cpp");
1446 CDB.addFile("A.cppm", R"cpp(
1447export module A;
1448import M;
1449export int AValue = MValue;
1450 )cpp");
1451
1452 llvm::SmallString<256> OrphanPCMPath;
1453 {
1454 ModulesBuilder Builder(CDB);
1455 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1456 ASSERT_TRUE(AInfo);
1457 HeaderSearchOptions HS(TestDir);
1458 AInfo->adjustHeaderSearchOptions(HS);
1459 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1460
1461 OrphanPCMPath = HS.PrebuiltModuleFiles["M"];
1462 llvm::sys::path::remove_filename(OrphanPCMPath);
1463 llvm::sys::path::append(OrphanPCMPath, "Orphan.pcm");
1464
1465 std::error_code EC;
1466 llvm::raw_fd_ostream OS(OrphanPCMPath, EC);
1467 ASSERT_FALSE(EC);
1468 OS << "orphan";
1469 OS.close();
1470 EXPECT_TRUE(llvm::sys::fs::exists(OrphanPCMPath));
1471
1472 int FD = -1;
1473 ASSERT_FALSE(llvm::sys::fs::openFileForWrite(OrphanPCMPath, FD,
1474 llvm::sys::fs::CD_OpenExisting,
1475 llvm::sys::fs::OF_None));
1476 auto CloseFD = llvm::scope_exit(
1477 [&] { llvm::sys::Process::SafelyCloseFileDescriptor(FD); });
1478 llvm::sys::TimePoint<> OldTime =
1479 std::chrono::system_clock::now() - std::chrono::hours(24 * 5);
1480 ASSERT_FALSE(llvm::sys::fs::setLastAccessAndModificationTime(FD, OldTime));
1481 }
1482
1483 ModulesBuilder Builder(CDB);
1484 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1485 ASSERT_TRUE(AInfo);
1486 EXPECT_FALSE(llvm::sys::fs::exists(OrphanPCMPath));
1487}
1488
1489TEST_F(PrerequisiteModulesTests,
1490 PersistentModuleCacheGCKeepsRecentStablePublishedModule) {
1491 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1492
1493 CDB.addFile("M.cppm", R"cpp(
1494export module M;
1495export int MValue = 43;
1496 )cpp");
1497 CDB.addFile("A.cppm", R"cpp(
1498export module A;
1499import M;
1500export int AValue = MValue;
1501 )cpp");
1502
1503 llvm::SmallString<256> OrphanPCMPath;
1504 {
1505 ModulesBuilder Builder(CDB);
1506 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1507 ASSERT_TRUE(AInfo);
1508 HeaderSearchOptions HS(TestDir);
1509 AInfo->adjustHeaderSearchOptions(HS);
1510 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1511
1512 OrphanPCMPath = HS.PrebuiltModuleFiles["M"];
1513 llvm::sys::path::remove_filename(OrphanPCMPath);
1514 llvm::sys::path::append(OrphanPCMPath, "Orphan.pcm");
1515
1516 std::error_code EC;
1517 llvm::raw_fd_ostream OS(OrphanPCMPath, EC);
1518 ASSERT_FALSE(EC);
1519 OS << "orphan";
1520 OS.close();
1521 EXPECT_TRUE(llvm::sys::fs::exists(OrphanPCMPath));
1522 }
1523
1524 ModulesBuilder Builder(CDB);
1525 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1526 ASSERT_TRUE(AInfo);
1527 EXPECT_TRUE(llvm::sys::fs::exists(OrphanPCMPath));
1528}
1529
1530TEST_F(PrerequisiteModulesTests,
1531 PersistentModuleCacheGCRemovesOldVersionedModuleFile) {
1532 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1533
1534 CDB.addFile("M.cppm", R"cpp(
1535export module M;
1536export int MValue = 43;
1537 )cpp");
1538 CDB.addFile("A.cppm", R"cpp(
1539export module A;
1540import M;
1541export int AValue = MValue;
1542 )cpp");
1543
1544 llvm::SmallString<256> OldVersionedPCMPath;
1545 {
1546 ModulesBuilder Builder(CDB);
1547 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1548 ASSERT_TRUE(AInfo);
1549 HeaderSearchOptions HS(TestDir);
1550 AInfo->adjustHeaderSearchOptions(HS);
1551 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1552
1553 OldVersionedPCMPath = HS.PrebuiltModuleFiles["M"];
1554 ASSERT_TRUE(llvm::sys::fs::exists(OldVersionedPCMPath));
1555
1556 int FD = -1;
1557 ASSERT_FALSE(llvm::sys::fs::openFileForWrite(OldVersionedPCMPath, FD,
1558 llvm::sys::fs::CD_OpenExisting,
1559 llvm::sys::fs::OF_None));
1560 auto CloseFD = llvm::scope_exit(
1561 [&] { llvm::sys::Process::SafelyCloseFileDescriptor(FD); });
1562 llvm::sys::TimePoint<> OldTime =
1563 std::chrono::system_clock::now() - std::chrono::hours(24 * 5);
1564 ASSERT_FALSE(llvm::sys::fs::setLastAccessAndModificationTime(FD, OldTime));
1565 }
1566
1567 ModulesBuilder Builder(CDB);
1568 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1569 ASSERT_TRUE(AInfo);
1570 EXPECT_FALSE(llvm::sys::fs::exists(OldVersionedPCMPath));
1571}
1572
1573TEST_F(PrerequisiteModulesTests,
1574 PersistentModuleCacheGCKeepsRecentVersionedModuleFile) {
1575 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1576
1577 CDB.addFile("M.cppm", R"cpp(
1578export module M;
1579export int MValue = 43;
1580 )cpp");
1581 CDB.addFile("A.cppm", R"cpp(
1582export module A;
1583import M;
1584export int AValue = MValue;
1585 )cpp");
1586
1587 auto FirstBuilder = std::make_unique<ModulesBuilder>(CDB);
1588 auto AInfo =
1589 FirstBuilder->buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1590 ASSERT_TRUE(AInfo);
1591 HeaderSearchOptions HS(TestDir);
1592 AInfo->adjustHeaderSearchOptions(HS);
1593 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1594 llvm::StringRef CopyOnReadPCMPath = HS.PrebuiltModuleFiles["M"];
1595 ASSERT_TRUE(llvm::sys::fs::exists(CopyOnReadPCMPath));
1596
1597 ModulesBuilder SecondBuilder(CDB);
1598 auto SecondInfo =
1599 SecondBuilder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1600 ASSERT_TRUE(SecondInfo);
1601 EXPECT_TRUE(llvm::sys::fs::exists(CopyOnReadPCMPath));
1602}
1603
1604TEST_F(PrerequisiteModulesTests,
1605 PersistentModuleCacheIgnoresRequiredSourceForOnDiskPath) {
1606 ModuleUnitRootCompilationDatabase CDB(TestDir, FS);
1607
1608 CDB.addFile("shared/M.cppm", R"cpp(
1609export module M;
1610export int MValue = 43;
1611 )cpp");
1612 CDB.addFile("projA/A.cppm", R"cpp(
1613export module A;
1614import M;
1615export int AValue = MValue;
1616 )cpp");
1617 CDB.addFile("projB/B.cppm", R"cpp(
1618export module B;
1619import M;
1620export int BValue = MValue;
1621 )cpp");
1622
1623 ModulesBuilder Builder(CDB);
1624
1625 auto AInfo =
1626 Builder.buildPrerequisiteModulesFor(getFullPath("projA/A.cppm"), FS);
1627 auto BInfo =
1628 Builder.buildPrerequisiteModulesFor(getFullPath("projB/B.cppm"), FS);
1629 ASSERT_TRUE(AInfo);
1630 ASSERT_TRUE(BInfo);
1631
1632 HeaderSearchOptions HSA(TestDir);
1633 HeaderSearchOptions HSB(TestDir);
1634 AInfo->adjustHeaderSearchOptions(HSA);
1635 BInfo->adjustHeaderSearchOptions(HSB);
1636 ASSERT_EQ(HSA.PrebuiltModuleFiles.count("M"), 1u);
1637 ASSERT_EQ(HSB.PrebuiltModuleFiles.count("M"), 1u);
1638 EXPECT_TRUE(llvm::sys::fs::exists(HSA.PrebuiltModuleFiles["M"]));
1639 EXPECT_TRUE(llvm::sys::fs::exists(HSB.PrebuiltModuleFiles["M"]));
1640
1641 llvm::SmallString<256> ExpectedRoot(getFullPath("shared/M.cppm"));
1642 llvm::sys::path::remove_filename(ExpectedRoot);
1643 llvm::sys::path::append(ExpectedRoot, ".cache", "clangd", "modules");
1644 EXPECT_TRUE(
1645 StringRef(HSA.PrebuiltModuleFiles["M"]).starts_with(ExpectedRoot));
1646 EXPECT_TRUE(StringRef(HSA.PrebuiltModuleFiles["M"]).contains("M.cppm-"));
1647 EXPECT_TRUE(
1648 StringRef(HSB.PrebuiltModuleFiles["M"]).starts_with(ExpectedRoot));
1649 EXPECT_TRUE(StringRef(HSB.PrebuiltModuleFiles["M"]).contains("M.cppm-"));
1650}
1651
1652TEST_F(PrerequisiteModulesTests, ModuleImportThroughInclude) {
1653 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1654
1655 Annotations UseCpp(R"cpp(
1656#include "Header.hpp"
1657void use() {
1658 TypeFrom^Module t1;
1659 TypeFromHeader t2;
1660}
1661)cpp");
1662
1663 CDB.addFile("M.cppm", R"cpp(
1664export module M;
1665export struct TypeFromModule {};
1666)cpp");
1667
1668 CDB.addFile("Header.hpp", R"cpp(
1669import M;
1670struct TypeFromHeader {};
1671)cpp");
1672
1673 CDB.addFile("Use.cpp", UseCpp.code());
1674
1675 ModulesBuilder Builder(CDB);
1676
1677 auto Inputs = getInputs("Use.cpp", CDB);
1678 Inputs.ModulesManager = &Builder;
1679 Inputs.Opts.SkipPreambleBuild = true;
1680
1681 auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
1682 ASSERT_TRUE(CI);
1683
1684 auto Preamble =
1685 buildPreamble(getFullPath("Use.cpp"), *CI, Inputs, /*StoreInMemory=*/true,
1686 /*PeambleCallback=*/nullptr);
1687 ASSERT_TRUE(Preamble);
1688 EXPECT_EQ(Preamble->Preamble.getBounds().Size, 0u);
1689
1690 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Inputs, std::move(CI), {},
1691 Preamble);
1692 ASSERT_TRUE(AST);
1693
1694 auto Result = codeComplete(getFullPath("Use.cpp"), UseCpp.point(),
1695 Preamble.get(), Inputs, {});
1696 EXPECT_THAT(Result.Completions,
1697 testing::UnorderedElementsAre(named("TypeFromModule"),
1698 named("TypeFromHeader")));
1699}
1700
1701TEST_F(PrerequisiteModulesTests,
1702 SkipPreambleBuildInvalidatedByNewModuleImport) {
1703 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1704
1705 CDB.addFile("Dep.cppm", R"cpp(
1706export module Dep;
1707)cpp");
1708
1709 CDB.addFile("Consumer.cpp", R"cpp(
1710import Dep;
1711void use() {}
1712)cpp");
1713
1714 ModulesBuilder Builder(CDB);
1715
1716 auto Inputs = getInputs("Consumer.cpp", CDB);
1717 Inputs.ModulesManager = &Builder;
1718 Inputs.Opts.SkipPreambleBuild = true;
1719
1720 auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
1721 ASSERT_TRUE(CI);
1722
1723 auto Preamble = buildPreamble(getFullPath("Consumer.cpp"), *CI, Inputs,
1724 /*StoreInMemory=*/true,
1725 /*PreambleCallback=*/nullptr);
1726 ASSERT_TRUE(Preamble);
1727 EXPECT_EQ(Preamble->Preamble.getBounds().Size, 0u);
1728 ASSERT_TRUE(Preamble->RequiredModules);
1729
1730 CDB.addFile("NewDep.cppm", R"cpp(
1731export module NewDep;
1732)cpp");
1733
1734 // Add a new import.
1735 Inputs.Contents = R"cpp(
1736import Dep;
1737import NewDep;
1738void use() {}
1739)cpp";
1740
1741 {
1742 std::error_code EC;
1743 llvm::raw_fd_ostream OS(getFullPath("Consumer.cpp"), EC,
1744 llvm::sys::fs::OF_None);
1745 ASSERT_FALSE(EC);
1746 OS << Inputs.Contents;
1747 }
1748
1749 auto NewCI = buildCompilerInvocation(Inputs, DiagConsumer);
1750 ASSERT_TRUE(NewCI);
1751
1752 EXPECT_FALSE(isPreambleCompatible(*Preamble, Inputs,
1753 getFullPath("Consumer.cpp"), *NewCI));
1754}
1755
1756TEST_F(PrerequisiteModulesTests, ModuleSemanticHighlighting) {
1757 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1758
1759 llvm::StringRef AnnotatedCode = R"cpp(
1760 module;
1761 $import[[import]] M;
1762 export module highlight;
1763 $export[[export]] void foo() {
1764 }
1765)cpp";
1766 Annotations UseCpp(AnnotatedCode);
1767
1768 CDB.addFile("M.cppm", R"cpp(
1769export module M;
1770export struct TypeFromModule {};
1771)cpp");
1772
1773 CDB.addFile("Use.cpp", UseCpp.code());
1774
1775 ModulesBuilder Builder(CDB);
1776
1777 auto Inputs = getInputs("Use.cpp", CDB);
1778 Inputs.ModulesManager = &Builder;
1779 Inputs.Opts.SkipPreambleBuild = true;
1780
1781 auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
1782 ASSERT_TRUE(CI);
1783
1784 auto Preamble =
1785 buildPreamble(getFullPath("Use.cpp"), *CI, Inputs, /*StoreInMemory=*/true,
1786 /*PeambleCallback=*/nullptr);
1787 ASSERT_TRUE(Preamble);
1788
1789 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Inputs, std::move(CI), {},
1790 Preamble);
1791
1792 ASSERT_TRUE(AST);
1793
1794 auto Actual = getSemanticHighlightings(AST.value(),
1795 /*IncludeInactiveRegionTokens=*/true);
1796 auto HasToken = [&](llvm::StringRef Name, HighlightingKind Kind) {
1797 return llvm::any_of(Actual, [&](const HighlightingToken &T) {
1798 return T.Kind == Kind && T.R == UseCpp.range(Name);
1799 });
1800 };
1801 EXPECT_TRUE(HasToken("import", HighlightingKind::Modifier));
1802 EXPECT_TRUE(HasToken("export", HighlightingKind::Modifier));
1803}
1804
1805} // namespace
1806} // namespace clang::clangd
1807
1808#endif
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
std::string CommandLine
Provides compilation arguments used for parsing C and C++ files.
static std::optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
An interface to query the modules information in the project.
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(std::nullopt_t CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
@ Info
An information message.
Definition Protocol.h:755
std::vector< HighlightingToken > getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens)
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
Definition TestTU.cpp:220
std::string maybeCaseFoldPath(PathRef Path)
Definition Path.cpp:18
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
Definition Compiler.cpp:96
MATCHER_P(named, N, "")
bool isPreambleCompatible(const PreambleData &Preamble, const ParseInputs &Inputs, PathRef FileName, const CompilerInvocation &CI)
Returns true if Preamble is reusable for Inputs.
Definition Preamble.cpp:723
std::vector< LocatedSymbol > locateSymbolAt(ParsedAST &AST, Position Pos, const SymbolIndex *Index)
Get definition of symbol at a specified Pos.
Definition XRefs.cpp:873
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats)
Build a preamble for the new inputs unless an old one can be reused.
Definition Preamble.cpp:573
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
std::string Path
A typedef to represent a file path.
Definition Path.h:26
CodeCompleteResult codeComplete(PathRef FileName, Position Pos, const PreambleData *Preamble, const ParseInputs &ParseInput, CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind)
Gets code completions at a specified Pos in FileName.
SignatureHelp signatureHelp(PathRef FileName, Position Pos, const PreambleData &Preamble, const ParseInputs &ParseInput, MarkupKind DocumentationFormat)
Get signature help at a specified Pos in FileName.
std::unique_ptr< ProjectModules > getProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Creates the project-modules facade used by clangd.
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition Protocol.cpp:46