diff --git a/include/Compiler/Compilation.h b/include/Compiler/Compilation.h index 6fae2b64..039d88cb 100644 --- a/include/Compiler/Compilation.h +++ b/include/Compiler/Compilation.h @@ -45,15 +45,8 @@ struct CompilationParams { } }; -namespace impl { - -/// Create a compiler invocation from the given compilation parameters. -std::unique_ptr createInvocation(CompilationParams& params); - -/// Create a compiler instance from the given compilation parameters. -std::unique_ptr createInstance(CompilationParams& params); - -} // namespace impl +/// Only preprocess ths source flie. +std::expected preprocess(CompilationParams& params); /// Build AST from given file path and content. If pch or pcm provided, apply them to the compiler. /// Note this function will not check whether we need to update the PCH or PCM, caller should check diff --git a/include/Compiler/Directive.h b/include/Compiler/Directive.h index ea550af8..6cc7335e 100644 --- a/include/Compiler/Directive.h +++ b/include/Compiler/Directive.h @@ -115,12 +115,25 @@ struct Pragma { clang::SourceLocation loc; }; +struct Import { + /// The name of imported module. + std::string name; + + /// The location of import keyword, may comes from macro expansion. + clang::SourceLocation location; + + /// The locations of tokens that make up the token name, may comes + /// from macro expansion. + std::vector name_locations; +}; + struct Directive { std::vector includes; std::vector hasIncludes; std::vector conditions; std::vector macros; std::vector pragmas; + std::vector imports; /// Tell preprocessor to collect directives information and store them in `directives`. static void attach(clang::Preprocessor& pp, diff --git a/include/Index/IncludeGraph.h b/include/Index/IncludeGraph.h index 399c9c08..887950cf 100644 --- a/include/Index/IncludeGraph.h +++ b/include/Index/IncludeGraph.h @@ -42,7 +42,7 @@ struct IncludeGraph { return paths[path_ref]; } - std::uint32_t getInclude(clang::FileID fid) { + std::uint32_t getInclude(clang::FileID fid) const { auto it = file_table.find(fid); assert(it != file_table.end()); return it->second; diff --git a/src/Compiler/Compilation.cpp b/src/Compiler/Compilation.cpp index e6e724df..dbe9f33d 100644 --- a/src/Compiler/Compilation.cpp +++ b/src/Compiler/Compilation.cpp @@ -6,7 +6,7 @@ namespace clice { -namespace impl { +namespace { std::unique_ptr createInvocation(CompilationParams& params) { llvm::SmallString<1024> buffer; @@ -91,10 +91,6 @@ std::unique_ptr createInstance(CompilationParams& param return instance; } -} // namespace impl - -namespace { - /// Execute given action with the on the given instance. `callback` is called after /// `BeginSourceFile`. Beacuse `BeginSourceFile` may create new preprocessor. std::expected ExecuteAction(clang::CompilerInstance& instance, @@ -166,14 +162,19 @@ std::expected ExecuteAction(std::unique_ptr preprocess(CompilationParams& params) { + auto instance = createInstance(params); + return ExecuteAction(std::move(instance), std::make_unique()); +} + std::expected compile(CompilationParams& params) { - auto instance = impl::createInstance(params); + auto instance = createInstance(params); return ExecuteAction(std::move(instance), std::make_unique()); } std::expected compile(CompilationParams& params, clang::CodeCompleteConsumer* consumer) { - auto instance = impl::createInstance(params); + auto instance = createInstance(params); auto& [file, offset] = params.completion; @@ -211,7 +212,7 @@ std::expected compile(CompilationParams& params, std::expected compile(CompilationParams& params, PCHInfo& out) { assert(params.bound.has_value() && "Preamble bounds is required to build PCH"); - auto instance = impl::createInstance(params); + auto instance = createInstance(params); llvm::StringRef outPath = params.outPath.str(); @@ -234,13 +235,12 @@ std::expected compile(CompilationParams& params, PCHInfo& } std::expected compile(CompilationParams& params, PCMInfo& out) { - auto instance = impl::createInstance(params); + auto instance = createInstance(params); /// Set options to generate PCM. instance->getFrontendOpts().OutputFile = params.outPath.str(); instance->getFrontendOpts().ProgramAction = clang::frontend::GenerateReducedModuleInterface; - ; if(auto info = ExecuteAction(std::move(instance), std::make_unique())) { assert(info->pp().isInNamedInterfaceUnit() && diff --git a/src/Compiler/Directive.cpp b/src/Compiler/Directive.cpp index 2d528f77..f386d9d6 100644 --- a/src/Compiler/Directive.cpp +++ b/src/Compiler/Directive.cpp @@ -7,73 +7,60 @@ namespace clice { namespace { -struct PPCallback : public clang::PPCallbacks { - clang::FileID prevFID; - clang::Preprocessor& PP; - clang::SourceManager& SM; - llvm::DenseMap& directives; - llvm::DenseMap macroCache; - - PPCallback(clang::Preprocessor& PP, llvm::DenseMap& directives) : +class DirectiveCollector : public clang::PPCallbacks { +public: + DirectiveCollector(clang::Preprocessor& PP, + llvm::DenseMap& directives) : PP(PP), SM(PP.getSourceManager()), directives(directives) {} - void addCondition(clang::SourceLocation loc, - Condition::BranchKind kind, - Condition::ConditionValue value, - clang::SourceRange conditionRange) { - auto& directive = directives[PP.getSourceManager().getFileID(loc)]; - directive.conditions.emplace_back(Condition{kind, value, loc, conditionRange}); +private: + void add_condition(clang::SourceLocation location, + Condition::BranchKind kind, + Condition::ConditionValue value, + clang::SourceRange cond_range) { + auto& directive = directives[SM.getFileID(location)]; + directive.conditions.emplace_back(kind, value, location, cond_range); } - void addCondition(clang::SourceLocation loc, - Condition::BranchKind kind, - clang::PPCallbacks::ConditionValueKind value, - clang::SourceRange conditionRange) { - Condition::ConditionValue condValue = Condition::None; - switch(value) { - case clang::PPCallbacks::CVK_False: { - condValue = Condition::False; - break; - } - case clang::PPCallbacks::CVK_True: { - condValue = Condition::True; - break; - } - case clang::PPCallbacks::CVK_NotEvaluated: { - condValue = Condition::Skipped; - break; - } - } - addCondition(loc, kind, condValue, conditionRange); + void add_condition(clang::SourceLocation loc, + Condition::BranchKind kind, + clang::PPCallbacks::ConditionValueKind value, + clang::SourceRange conditionRange) { + Condition::ConditionValue cond_value = + value == clang::PPCallbacks::CVK_False ? Condition::None + : value == clang::PPCallbacks::CVK_True ? Condition::True + : value == clang::PPCallbacks::CVK_NotEvaluated ? Condition::Skipped + : Condition::None; + add_condition(loc, kind, cond_value, conditionRange); } - void addCondition(clang::SourceLocation loc, - Condition::BranchKind kind, - const clang::Token& name, - const clang::MacroDefinition& definition) { + void add_condition(clang::SourceLocation loc, + Condition::BranchKind kind, + const clang::Token& name, + const clang::MacroDefinition& definition) { if(auto def = definition.getMacroInfo()) { - addMacro(def, MacroRef::Ref, name.getLocation()); - addCondition(loc, kind, Condition::True, name.getLocation()); + add_macro(def, MacroRef::Ref, name.getLocation()); + add_condition(loc, kind, Condition::True, name.getLocation()); } else { - addCondition(loc, kind, Condition::False, name.getLocation()); + add_condition(loc, kind, Condition::False, name.getLocation()); } } - void addMacro(const clang::MacroInfo* def, MacroRef::Kind kind, clang::SourceLocation loc) { + void add_macro(const clang::MacroInfo* def, MacroRef::Kind kind, clang::SourceLocation loc) { if(def->isBuiltinMacro()) { return; } - if(PP.getSourceManager().isWrittenInBuiltinFile(loc) || - PP.getSourceManager().isWrittenInCommandLineFile(loc) || - PP.getSourceManager().isWrittenInScratchSpace(loc)) { + if(SM.isWrittenInBuiltinFile(loc) || SM.isWrittenInCommandLineFile(loc) || + SM.isWrittenInScratchSpace(loc)) { return; } - auto& directive = directives[PP.getSourceManager().getFileID(loc)]; + auto& directive = directives[SM.getFileID(loc)]; directive.macros.emplace_back(MacroRef{def, kind, loc}); } +public: /// ============================================================================ /// Rewritten Preprocessor Callbacks /// ============================================================================ @@ -130,6 +117,18 @@ struct PPCallback : public clang::PPCallbacks { } } + void moduleImport(clang::SourceLocation import_location, + clang::ModuleIdPath names, + const clang::Module*) override { + auto fid = SM.getFileID(SM.getExpansionLoc(import_location)); + auto& import = directives[fid].imports.emplace_back(); + import.location = import_location; + for(auto& [name, location]: names) { + import.name += name->getName(); + import.name_locations.emplace_back(location); + } + } + void HasInclude(clang::SourceLocation location, llvm::StringRef, bool, @@ -167,71 +166,71 @@ struct PPCallback : public clang::PPCallbacks { } void If(clang::SourceLocation loc, - clang::SourceRange conditionRange, + clang::SourceRange cond_range, clang::PPCallbacks::ConditionValueKind value) override { - addCondition(loc, Condition::If, value, conditionRange); + add_condition(loc, Condition::If, value, cond_range); } void Elif(clang::SourceLocation loc, - clang::SourceRange conditionRange, + clang::SourceRange cond_range, clang::PPCallbacks::ConditionValueKind value, clang::SourceLocation) override { - addCondition(loc, Condition::Elif, value, conditionRange); + add_condition(loc, Condition::Elif, value, cond_range); } void Ifdef(clang::SourceLocation loc, const clang::Token& name, const clang::MacroDefinition& definition) override { - addCondition(loc, Condition::Ifdef, name, definition); + add_condition(loc, Condition::Ifdef, name, definition); } /// Invoke when #elifdef branch is taken. void Elifdef(clang::SourceLocation loc, const clang::Token& name, const clang::MacroDefinition& definition) override { - addCondition(loc, Condition::Elifdef, name, definition); + add_condition(loc, Condition::Elifdef, name, definition); } /// Invoke when #elif is skipped. void Elifdef(clang::SourceLocation loc, - clang::SourceRange conditionRange, + clang::SourceRange cond_range, clang::SourceLocation) override { /// FIXME: should we try to evaluate the condition to compute the macro reference? - addCondition(loc, Condition::Elifdef, Condition::Skipped, conditionRange); + add_condition(loc, Condition::Elifdef, Condition::Skipped, cond_range); } /// Invoke when #ifndef is taken. void Ifndef(clang::SourceLocation loc, const clang::Token& name, const clang::MacroDefinition& definition) override { - addCondition(loc, Condition::Ifndef, name, definition); + add_condition(loc, Condition::Ifndef, name, definition); } // Invoke when #elifndef is taken. void Elifndef(clang::SourceLocation loc, const clang::Token& name, const clang::MacroDefinition& definition) override { - addCondition(loc, Condition::Elifndef, name, definition); + add_condition(loc, Condition::Elifndef, name, definition); } // Invoke when #elifndef is skipped. void Elifndef(clang::SourceLocation loc, - clang::SourceRange conditionRange, + clang::SourceRange cond_range, clang::SourceLocation) override { - addCondition(loc, Condition::Elifndef, Condition::Skipped, conditionRange); + add_condition(loc, Condition::Elifndef, Condition::Skipped, cond_range); } void Else(clang::SourceLocation loc, clang::SourceLocation ifLoc) override { - addCondition(loc, Condition::Else, Condition::None, clang::SourceRange()); + add_condition(loc, Condition::Else, Condition::None, clang::SourceRange()); } void Endif(clang::SourceLocation loc, clang::SourceLocation ifLoc) override { - addCondition(loc, Condition::EndIf, Condition::None, clang::SourceRange()); + add_condition(loc, Condition::EndIf, Condition::None, clang::SourceRange()); } void MacroDefined(const clang::Token& name, const clang::MacroDirective* MD) override { if(auto def = MD->getMacroInfo()) { - addMacro(def, MacroRef::Def, name.getLocation()); + add_macro(def, MacroRef::Def, name.getLocation()); } } @@ -240,7 +239,7 @@ struct PPCallback : public clang::PPCallbacks { clang::SourceRange range, const clang::MacroArgs* args) override { if(auto def = definition.getMacroInfo()) { - addMacro(def, MacroRef::Ref, name.getLocation()); + add_macro(def, MacroRef::Ref, name.getLocation()); } } @@ -248,16 +247,23 @@ struct PPCallback : public clang::PPCallbacks { const clang::MacroDefinition& MD, const clang::MacroDirective* undef) override { if(auto def = MD.getMacroInfo()) { - addMacro(def, MacroRef::Undef, name.getLocation()); + add_macro(def, MacroRef::Undef, name.getLocation()); } } + +private: + clang::FileID prevFID; + clang::Preprocessor& PP; + clang::SourceManager& SM; + llvm::DenseMap& directives; + llvm::DenseMap macroCache; }; } // namespace void Directive::attach(clang::Preprocessor& pp, llvm::DenseMap& directives) { - pp.addPPCallbacks(std::make_unique(pp, directives)); + pp.addPPCallbacks(std::make_unique(pp, directives)); } } // namespace clice diff --git a/src/Compiler/Module.cpp b/src/Compiler/Module.cpp index f9c604f2..61d14360 100644 --- a/src/Compiler/Module.cpp +++ b/src/Compiler/Module.cpp @@ -97,33 +97,16 @@ std::string scanModuleName(CompilationParams& params) { } std::expected scanModule(CompilationParams& params) { - struct ModuleCollector : public clang::PPCallbacks { - ModuleInfo& info; - - ModuleCollector(ModuleInfo& info) : info(info) {} - - void moduleImport(clang::SourceLocation importLoc, - clang::ModuleIdPath path, - const clang::Module* imported) override { - assert(path.size() == 1); - info.mods.emplace_back(path[0].first->getName()); - } - }; - ModuleInfo info; - clang::PreprocessOnlyAction action; - auto instance = impl::createInstance(params); - - if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { - return std::unexpected("Failed to begin source file"); + auto AST = preprocess(params); + if(!AST) { + return std::unexpected(AST.error()); } - auto& pp = instance->getPreprocessor(); + auto&& pp = AST->pp(); - pp.addPPCallbacks(std::make_unique(info)); - - if(auto error = action.Execute()) { - return std::unexpected(std::format("{}", error)); + for(auto& import: AST->directives()[AST->getInterestedFile()].imports) { + info.mods.emplace_back(import.name); } if(pp.isInNamedModule()) { diff --git a/unittests/Compiler/Preamble.cpp b/unittests/Compiler/Preamble.cpp index 44dab18b..9462d77d 100644 --- a/unittests/Compiler/Preamble.cpp +++ b/unittests/Compiler/Preamble.cpp @@ -6,213 +6,296 @@ namespace clice::testing { namespace { -TEST(Preamble, ComputePreambleBound) { - Annotation annotation = {""}; - - auto test = [](llvm::StringRef content, - std::vector marks, +void EXPECT_BOUNDS(std::vector marks, + llvm::StringRef content, LocationChain chain = LocationChain()) { - Annotation annotation{content}; - auto bounds = computePreambleBounds(annotation.source()); + Annotation annotation{content}; + auto bounds = computePreambleBounds(annotation.source()); - clice::println("{}", dump(bounds)); + ASSERT_EQ(bounds.size(), marks.size(), chain); - EXPECT_EQ(bounds.size(), marks.size(), chain); + for(std::uint32_t i = 0; i < bounds.size(); i++) { + EXPECT_EQ(bounds[i], annotation.offset(marks[i]), chain); + } +} - for(std::uint32_t i = 0; i < bounds.size(); i++) { - EXPECT_EQ(bounds[i], annotation.offset(marks[i]), chain); +llvm::StringMap scan(llvm::StringRef content) { + llvm::StringMap files; + std::string current_filename; + std::string current_content; + + auto save_previous_file = [&]() { + if(current_filename.empty()) { + return; } + + files.try_emplace(current_filename, llvm::StringRef(current_content).trim()); + current_filename.clear(); + current_content.clear(); }; - annotation = {"#include $(end)"}; + while(!content.empty()) { + llvm::StringRef line = content.take_front(content.find_first_of("\r\n")); + content = content.drop_front(line.size()); + if(content.starts_with("\r\n")) { + content = content.drop_front(2); + } else if(content.starts_with("\n")) { + content = content.drop_front(1); + } - test("#include $(0)", {"0"}); - test("#include $(0)\n", {"0"}); + if(line.starts_with("#[") && line.ends_with("]")) { + save_previous_file(); + current_filename = line.slice(2, line.size() - 1).str(); + } else if(!current_filename.empty()) { + current_content += line; + current_content += '\n'; + } + } - test(R"cpp( + save_previous_file(); + + return files; +} + +void EXPECT_BUILD_PCH(llvm::StringRef main_file, + llvm::StringRef test_contents, + llvm::StringRef preamble = "", + LocationChain chain = LocationChain()) { + auto tmp = fs::createTemporaryFile("clice", "pch"); + ASSERT_TRUE(tmp); + std::string outPath = std::move(*tmp); + + auto files = scan(test_contents); + if(!preamble.empty()) { + files.try_emplace("preamble.h", preamble); + } + + ASSERT_TRUE(files.contains(main_file)); + std::string content = files[main_file]; + files.erase(main_file); + + CompilationParams params; + params.srcPath = main_file; + params.content = content; + params.outPath = outPath; + params.bound = computePreambleBound(content); + + if(!preamble.empty()) { + params.command = std::format("clang++ -xc++ -std=c++20 --include=preamble.h {}", main_file); + } else { + params.command = std::format("clang++ -xc++ -std=c++20 {}", main_file); + } + + for(auto& [path, content]: files) { + params.addRemappedFile(path::join(".", path), content); + } + + /// Build PCH. + PCHInfo info; + { + /// NOTE: PCH file is written when CompilerInstance is destructed. + auto AST = compile(params, info); + ASSERT_TRUE(AST, chain); + + EXPECT_EQ(info.path, outPath, chain); + EXPECT_EQ(info.command, params.command, chain); + /// TODO: EXPECT_EQ(info.deps, deps); + } + + /// Build AST with PCH. + for(auto& [path, content]: files) { + params.addRemappedFile(path::join(".", path), content); + } + + params.bound.reset(); + params.pch = {info.path, info.preamble.size()}; + auto AST = compile(params); + ASSERT_TRUE(AST, chain); +} + +TEST(Preamble, Bounds) { + EXPECT_BOUNDS({"0"}, "#include $(0)"); + EXPECT_BOUNDS({"0"}, "#include $(0)\n"); + + EXPECT_BOUNDS({"0", "1"}, + R"cpp( #ifdef TEST #include $(0) #define 1 #endif$(1) - )cpp", - {"0", "1"}); +)cpp"); - test(R"cpp( + EXPECT_BOUNDS({"0"}, + R"cpp( #include $(0) int x = 1; - )cpp", - {"0"}); +)cpp"); - test(R"cpp( + EXPECT_BOUNDS({"0"}, R"cpp( module; #include $(0) export module test; - )cpp", - {"0"}); +)cpp"); } -TEST(Preamble, BuildPreambleForTU) { - auto outPath = path::join(".", "main.pch"); - llvm::StringRef command = "clang++ -std=c++20 main.cpp"; - - llvm::StringRef test = R"cpp( +TEST(Preamble, TranslationUnit) { + EXPECT_BUILD_PCH("main.cpp", + R"cpp( +#[test.h] int foo(); -)cpp"; - llvm::StringRef content = R"cpp(#include "test.h" +#[main.cpp] +#include "test.h" int x = foo(); -)cpp"; - - std::vector deps = {path::join(".", "test.h")}; - - CompilationParams params; - params.outPath = outPath; - params.srcPath = "main.cpp"; - params.content = content; - params.command = command; - params.bound = computePreambleBound(content); - - llvm::SmallString<128> path; - params.addRemappedFile(deps[0], test); - - /// Build PCH. - PCHInfo out; - { - auto info = compile(params, out); - EXPECT_TRUE(bool(info)); - - EXPECT_EQ(out.path, outPath); - EXPECT_EQ(out.preamble, R"(#include "test.h")"); - EXPECT_EQ(out.command, command); - EXPECT_EQ(out.deps, deps); - } - - /// Build AST - { - params.bound.reset(); - params.pch = {outPath, out.preamble.size()}; - auto info = compile(params); - EXPECT_TRUE(bool(info)); - } +)cpp"); } -/// FIXME: headers not found -/// -/// TEST(Preamble, BuildChainedPreamble) { -/// llvm::StringRef content = R"( -/// #include -/// )"; -/// -/// CompilationParams params; -/// params.srcPath = "main.pch"; -/// params.content = content; -/// params.command = "clang++ -std=c++20 -xc++ main.pch"; -/// params.outPath = path::join(".", "header1.pch"); -/// params.bound = computePreambleBound(content); -/// -/// { -/// PCHInfo out; -/// auto AST = compile(params, out); -/// if(!AST) { -/// println("error: {}", AST.error()); -/// } -/// llvm::outs() << "bound: " << *params.bound << "\n"; -/// } -/// -/// content = R"( -/// #include -/// #include -/// )"; -/// -/// params.pch = std::pair{params.outPath.str(), *params.bound}; -/// params.content = content; -/// params.outPath = path::join(".", "header2.pch"); -/// params.bound = computePreambleBound(content); -/// -/// { -/// PCHInfo out; -/// auto AST = compile(params, out); -/// if(!AST) { -/// println("error: {}", AST.error()); -/// } -/// llvm::outs() << "bound: " << *params.bound << "\n"; -/// } -/// -/// content = R"( -/// int main() { -/// auto y = abs(1.0); -/// return 0; -/// } -/// )"; -/// -/// params.pch = std::pair{params.outPath.str(), 0}; -/// params.srcPath = "main.cpp"; -/// params.command = "clang++ -std=c++20 main.cpp"; -/// params.content = content; -/// params.outPath = path::join(".", "header2.pch"); -/// -/// { -/// auto AST = compile(params); -/// if(!AST) { -/// println("error: {}", AST.error()); -/// } -/// llvm::outs() << "bound: " << *params.bound << "\n"; -/// /// AST->tu()->dump(); -/// } -/// } - -TEST(Preamble, BuildPreambleForHeader) { - /// TODO: The key point is find interested file according to the header context. -} - -TEST(Preamble, BuildPreambleForMU) { - auto outPath = path::join(".", "main.pch"); - llvm::StringRef command = "clang++ -std=c++20 main.cpp"; - - llvm::StringRef test = R"cpp( +TEST(Preamble, Module) { + EXPECT_BUILD_PCH("main.cpp", + R"cpp( +#[test.h] int foo(); -)cpp"; - llvm::StringRef content = R"cpp( +#[main.cpp] module; #include "test.h" export module test; export int x = foo(); +)cpp"); +} + +TEST(Preamble, Header) { + llvm::StringRef test_contents = R"cpp( +#[test.h] +int bar(); + +#[test1.h] +#include "test.h" +Point x = {foo(), bar()}; + +#[test2.h] +struct Point { + int x; + int y; +}; + +#include "test1.h" + +#[test3.h] +int foo(); + +#[main.cpp] +#include "test3.h" +#include "test2.h" )cpp"; - std::vector deps = {path::join(".", "test.h")}; + auto files = scan(test_contents); + ASSERT_TRUE(files.contains("main.cpp")); + std::string content = files["main.cpp"]; + files.erase("main.cpp"); + + std::string preamble; + + /// Compute implicit include. + { + + CompilationParams params; + params.content = content; + params.srcPath = "main.cpp"; + params.command = "clang++ -std=c++20 main.cpp"; + + for(auto& [path, file]: files) { + params.addRemappedFile(path::join(".", path), file); + } + + auto AST = preprocess(params); + ASSERT_TRUE(AST); + + auto& SM = AST->srcMgr(); + auto path = path::join(".", "test1.h"); + auto entry = SM.getFileManager().getFileRef(path); + ASSERT_TRUE(entry); + + auto fid = SM.translateFile(*entry); + ASSERT_TRUE(fid.isValid()); + + while(fid.isValid()) { + auto location = SM.getIncludeLoc(fid); + auto [fid2, offset] = AST->getDecomposedLoc(location); + auto content = AST->getFileContent(fid2).substr(0, offset); + + /// Remove incomplete include. + content = content.substr(0, content.rfind("\n")); + preamble += content; + fid = fid2; + } + } + + EXPECT_BUILD_PCH("test1.h", test_contents, preamble); +} + +TEST(Preamble, Chain) { + llvm::StringRef test_contents = R"cpp( +#[test.h] +int bar(); + +#[test2.h] +int foo(); + +#[main.cpp] +#include "test.h" +#include "test2.h" +int x = bar(); +int y = foo(); +)cpp"; + + auto files = scan(test_contents); + ASSERT_TRUE(files.contains("main.cpp")); + std::string content = files["main.cpp"]; + files.erase("main.cpp"); + + auto bounds = computePreambleBounds(content); CompilationParams params; - params.outPath = outPath; params.srcPath = "main.cpp"; params.content = content; - params.command = command; - params.bound = computePreambleBound(content); + params.command = "clang++ -std=c++20 main.cpp"; - llvm::SmallString<128> path; - params.addRemappedFile(deps[0], test); + PCHInfo info; + for(auto bound: bounds) { + auto tmp = fs::createTemporaryFile("clice", "pch"); + ASSERT_TRUE(tmp); + std::string outPath = std::move(*tmp); - /// Build PCH. - PCHInfo out; - { - auto info = compile(params, out); - EXPECT_TRUE(bool(info)); + if(params.bound && !params.outPath.empty()) { + params.pch = {params.outPath.str().str(), *params.bound}; + } - EXPECT_EQ(out.path, outPath); - EXPECT_EQ(out.preamble, llvm::StringRef(R"( -module; -#include "test.h")")); - EXPECT_EQ(out.command, command); - EXPECT_EQ(out.deps, deps); + params.outPath = outPath; + params.bound = bound; + + for(auto& [path, content]: files) { + params.addRemappedFile(path::join(".", path), content); + } + + { + auto AST = compile(params, info); + ASSERT_TRUE(AST); + + EXPECT_EQ(info.path, outPath); + EXPECT_EQ(info.command, params.command); + } } - /// Build AST - { - params.bound.reset(); - params.pch = {outPath, out.preamble.size()}; - auto info = compile(params); - EXPECT_TRUE(bool(info)); + /// Build AST with PCH. + for(auto& [path, content]: files) { + params.addRemappedFile(path::join(".", path), content); } + + params.bound.reset(); + params.pch = {info.path, info.preamble.size()}; + auto AST = compile(params); + ASSERT_TRUE(AST); } } // namespace