From d253c1f09972e17ff698b6c94109d902490d9d7b Mon Sep 17 00:00:00 2001 From: Shiyu Date: Mon, 6 Apr 2026 15:51:26 +0800 Subject: [PATCH] feat: collect #embed and __has_embed directives in PPCallbacks (#309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add `Embed` and `HasEmbed` structs to `directive.h` for storing `#embed` / `__has_embed` directive info - Implement `EmbedDirective` and `HasEmbed` PPCallbacks in `DirectiveCollector`, using the current `CompilationUnitRef` API - Add unit tests for both directives (including non-existent file handling) Rebased onto current main, resolving conflicts from the `Compiler/` → `compile/` restructuring and the `CompilationUnitRef` API migration. Original PR by @Guo-Shiyu. ## Test plan - [x] `pixi run unit-test` — 465 tests passed, 0 failures Co-authored-by: ykiko Co-authored-by: Claude Opus 4.6 (1M context) --- src/compile/directive.cpp | 25 ++++++++ src/compile/directive.h | 35 +++++++++++ tests/unit/compile/directive_tests.cpp | 82 ++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/src/compile/directive.cpp b/src/compile/directive.cpp index 44d0f153..cb0f764d 100644 --- a/src/compile/directive.cpp +++ b/src/compile/directive.cpp @@ -65,6 +65,31 @@ public: /// Rewritten Preprocessor Callbacks /// ============================================================================ + void HasEmbed(clang::SourceLocation location, + llvm::StringRef filename, + bool is_angled, + clang::OptionalFileEntryRef file) override { + unit->directives[unit.file_id(location)].has_embeds.emplace_back(clice::HasEmbed{ + .file_name = filename, + .file = file, + .is_angled = is_angled, + .loc = location, + }); + } + + void EmbedDirective(clang::SourceLocation location, + clang::StringRef filename, + bool is_angled, + clang::OptionalFileEntryRef file, + const clang::LexEmbedParametersResult&) override { + unit->directives[unit.file_id(location)].embeds.emplace_back(Embed{ + .file_name = filename, + .file = file, + .is_angled = is_angled, + .loc = location, + }); + } + void InclusionDirective(clang::SourceLocation hash_loc, const clang::Token& include_tok, llvm::StringRef, diff --git a/src/compile/directive.h b/src/compile/directive.h index b858bea8..633e6eb4 100644 --- a/src/compile/directive.h +++ b/src/compile/directive.h @@ -132,6 +132,39 @@ struct Import { std::vector name_locations; }; +/// Information about `#embed` directive. +struct Embed { + /// The file name in the embed directive, not including quotes or angle brackets. + llvm::StringRef file_name; + + /// The actual file that may be embedded by this embed directive. + clang::OptionalFileEntryRef file; + + /// Whether the file name is angled. + bool is_angled; + + /// Location of the `#` token. + clang::SourceLocation loc; + + /// TODO: Currently we do not store parameters of the embed directive. + /// See clang::LexEmbedParametersResult for details. +}; + +/// Information about `__has_embed` directive. +struct HasEmbed { + /// The file name in the embed directive, not including quotes or angle brackets. + llvm::StringRef file_name; + + /// The actual file that may be embedded by this embed directive. + clang::OptionalFileEntryRef file; + + /// Whether the file name is angled. + bool is_angled; + + /// Location of the `__has_embed` token. + clang::SourceLocation loc; +}; + struct Directive { std::vector includes; std::vector has_includes; @@ -139,6 +172,8 @@ struct Directive { std::vector macros; std::vector pragmas; std::vector imports; + std::vector embeds; + std::vector has_embeds; }; } // namespace clice diff --git a/tests/unit/compile/directive_tests.cpp b/tests/unit/compile/directive_tests.cpp index 7a53c73a..ffd94ea4 100644 --- a/tests/unit/compile/directive_tests.cpp +++ b/tests/unit/compile/directive_tests.cpp @@ -13,6 +13,8 @@ std::vector has_includes; std::vector conditions; std::vector macros; std::vector pragmas; +std::vector embeds; +std::vector has_embeds; using u32 = std::uint32_t; @@ -25,6 +27,8 @@ void run(llvm::StringRef code) { conditions = unit->directives()[fid].conditions; macros = unit->directives()[fid].macros; pragmas = unit->directives()[fid].pragmas; + embeds = unit->directives()[fid].embeds; + has_embeds = unit->directives()[fid].has_embeds; } void EXPECT_INCLUDE(u32 index, llvm::StringRef position, llvm::StringRef path) { @@ -66,6 +70,25 @@ void EXPECT_MACRO(u32 index, MacroRef::Kind kind, llvm::StringRef position) { ASSERT_EQ(offset, point(position)); } +void EXPECT_EMBED(u32 index, llvm::StringRef position, llvm::StringRef filename) { + auto& embed = embeds[index]; + auto [_, offset] = unit->decompose_location(embed.loc); + ASSERT_EQ(offset, point(position)); + ASSERT_TRUE(embed.file.has_value()); + ASSERT_EQ(embed.file_name, filename); +} + +void EXPECT_HAS_EMBED(u32 index, + llvm::StringRef position, + llvm::StringRef filename, + bool exists = true) { + auto& has_embed = has_embeds[index]; + auto [_, offset] = unit->decompose_location(has_embed.loc); + ASSERT_EQ(offset, point(position)); + ASSERT_EQ(has_embed.file.has_value(), exists); + ASSERT_EQ(has_embed.file_name, filename); +} + void EXPECT_PRAGMA(u32 index, Pragma::Kind kind, llvm::StringRef pos, llvm::StringRef text) { auto& pragma = pragmas[index]; auto [_, offset] = unit->decompose_location(pragma.loc); @@ -196,6 +219,65 @@ $(2)#pragma endregion EXPECT_PRAGMA(2, Pragma::Kind::EndRegion, "2", "#pragma endregion"); }; +TEST_CASE(Embed) { + run(R"cpp( +#[bytes10.bin] +0123456789 + +#[bytes5.bin] +ABCDE + +#[main.cpp] +const char e0 = { +$(0)#embed "bytes10.bin" +}; + +const char e1 = { +$(1)#embed "bytes10.bin" +}; + +const char e2 = { +$(2)#embed "bytes5.bin" +}; + +const char e3 = { +$(3)#embed "bytes5.bin" +}; + +const char e4 = { +$(4)#embed "non-existed.bin" +}; + +)cpp"); + + // e4 will not be processed by clang::PPCallbacks::EmbedDirective(), so there are only 4 + // embeds. + ASSERT_EQ(embeds.size(), 4U); + EXPECT_EMBED(0, "0", "bytes10.bin"); + EXPECT_EMBED(1, "1", "bytes10.bin"); + EXPECT_EMBED(2, "2", "bytes5.bin"); + EXPECT_EMBED(3, "3", "bytes5.bin"); +}; + +TEST_CASE(HasEmbed) { + run(R"cpp( +#[test.bin] + +#[main.cpp] +#embed "test.bin" + +#if __has_embed$(0)("test.bin") +#endif + +#if __has_embed$(1)("non-existed.bin") +#endif +)cpp"); + + ASSERT_EQ(has_embeds.size(), 2U); + EXPECT_HAS_EMBED(0, "0", "test.bin"); + EXPECT_HAS_EMBED(1, "1", "non-existed.bin", /*exists=*/false); +}; + }; // TEST_SUITE(Directive) } // namespace