diff --git a/xls/dslx/frontend/ast_cloner.cc b/xls/dslx/frontend/ast_cloner.cc index f46a35cadb..e79a18e340 100644 --- a/xls/dslx/frontend/ast_cloner.cc +++ b/xls/dslx/frontend/ast_cloner.cc @@ -1247,6 +1247,60 @@ class AstCloner : public AstNodeVisitor { absl::flat_hash_map old_to_new_; }; +absl::StatusOr MakeClonedModuleMember( + const ModuleMember& original, + const absl::flat_hash_map& old_to_new) { + ModuleMember new_member; + XLS_RETURN_IF_ERROR(absl::visit( + Visitor{ + [&](auto* node) -> absl::Status { + auto it = old_to_new.find(node); + if (it == old_to_new.end()) { + return absl::InternalError("Cloned node missing from map."); + } + AstNode* replacement = it->second; + if (replacement->kind() == AstNodeKind::kVerbatimNode) { + new_member = down_cast(replacement); + return absl::OkStatus(); + } + using NodeT = std::remove_pointer_t; + new_member = down_cast(replacement); + return absl::OkStatus(); + }, + }, + original)); + return new_member; +} + +bool ShouldSkipMember( + const ModuleMember& member, + const absl::flat_hash_set& nodes_to_ignore) { + if (nodes_to_ignore.empty()) { + return false; + } + const AstNode* member_node = ToAstNode(member); + if (nodes_to_ignore.contains(member_node)) { + return true; + } + for (NameDef* name_def : ModuleMemberGetNameDefs(member)) { + if (nodes_to_ignore.contains(static_cast(name_def))) { + return true; + } + } + return false; +} + +void CollectUseTreeEntries(const UseTreeEntry& entry, + absl::flat_hash_set& ignored_nodes) { + ignored_nodes.insert(&entry); + for (AstNode* child : entry.GetChildren(/*want_types=*/false)) { + if (child == nullptr || child->kind() != AstNodeKind::kUseTreeEntry) { + continue; + } + CollectUseTreeEntries(*down_cast(child), ignored_nodes); + } +} + } // namespace std::optional PreserveTypeDefinitionsReplacer( @@ -1335,6 +1389,106 @@ absl::StatusOr> CloneModule(const Module& module, return new_module; } +absl::StatusOr> CloneModuleIgnoreMembers( + const Module& module, absl::Span members_to_ignore) { + absl::flat_hash_set nodes_to_ignore; + nodes_to_ignore.reserve(members_to_ignore.size()); + for (const AstNode* node : members_to_ignore) { + if (node != nullptr) { + nodes_to_ignore.insert(node); + } + } + + absl::flat_hash_map ignored_name_defs; + absl::flat_hash_set ignored_nodes(nodes_to_ignore.begin(), + nodes_to_ignore.end()); + for (const ModuleMember& member : module.top()) { + if (!ShouldSkipMember(member, nodes_to_ignore)) { + continue; + } + const AstNode* member_node = ToAstNode(member); + ignored_nodes.insert(member_node); + for (NameDef* def : ModuleMemberGetNameDefs(member)) { + ignored_name_defs.emplace(def, def->identifier()); + ignored_nodes.insert(def); + } + if (auto* use = dynamic_cast(member_node)) { + CollectUseTreeEntries(use->root(), ignored_nodes); + } + } + + auto new_module = std::make_unique(module.name(), module.fs_path(), + *module.file_table()); + std::optional attribute_span = module.GetAttributeSpan(); + for (const ModuleAttribute& attribute : module.attributes()) { + new_module->AddAttribute(attribute, attribute_span); + } + + absl::flat_hash_map global_map; + + for (const ModuleMember& member : module.top()) { + if (ShouldSkipMember(member, nodes_to_ignore)) { + continue; + } + + const AstNode* original_node = ToAstNode(member); + CloneReplacer reuse_existing = + [&](const AstNode* node, Module* target, + const absl::flat_hash_map&) + -> absl::StatusOr> { + if (auto cached = global_map.find(node); cached != global_map.end()) { + return cached->second; + } + + if (node->kind() == AstNodeKind::kNameRef) { + const auto* name_ref = down_cast(node); + if (std::holds_alternative(name_ref->name_def())) { + const NameDef* def = std::get(name_ref->name_def()); + if (auto ignored_it = ignored_name_defs.find(def); + ignored_it != ignored_name_defs.end()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Module member references removed definition '%s'", + ignored_it->second)); + } + if (auto def_it = global_map.find(def); def_it != global_map.end()) { + auto* new_def = down_cast(def_it->second); + AstNode* new_ref = + target->Make(name_ref->span(), name_ref->identifier(), + new_def, name_ref->in_parens()); + global_map[node] = new_ref; + return new_ref; + } + } + } else if (node->kind() == AstNodeKind::kTypeRef) { + const auto* type_ref = down_cast(node); + const bool references_ignored = + absl::visit(Visitor{[&](auto* ref) { + return ref != nullptr && ignored_nodes.contains(ref); + }}, + type_ref->type_definition()); + if (references_ignored) { + return absl::InvalidArgumentError(absl::StrFormat( + "Module member references removed definition '%s'", + type_ref->ToString())); + } + } + + return std::optional{std::nullopt}; + }; + + XLS_ASSIGN_OR_RETURN(auto old_to_new, + CloneAstAndGetAllPairs(original_node, new_module.get(), + std::move(reuse_existing))); + global_map.insert(old_to_new.begin(), old_to_new.end()); + XLS_ASSIGN_OR_RETURN(ModuleMember cloned_member, + MakeClonedModuleMember(member, old_to_new)); + XLS_RETURN_IF_ERROR(new_module->AddTop(cloned_member, + /*make_collision_error=*/nullptr)); + } + + return new_module; +} + CloneReplacer ChainCloneReplacers(CloneReplacer first, CloneReplacer second) { return [first = std::move(first), second = std::move(second)]( diff --git a/xls/dslx/frontend/ast_cloner.h b/xls/dslx/frontend/ast_cloner.h index 07ae06c231..ae7a52edb8 100644 --- a/xls/dslx/frontend/ast_cloner.h +++ b/xls/dslx/frontend/ast_cloner.h @@ -112,6 +112,13 @@ CloneAstAndGetAllPairs(const AstNode* root, absl::StatusOr> CloneModule( const Module& module, CloneReplacer replacer = &NoopCloneReplacer); +// Returns a clone of `module` that omits any top-level members whose +// identifiers appear in `member_names_to_ignore`. References to those members +// are not validated or rewritten; the caller is responsible for handling any +// resulting dangling references. +absl::StatusOr> CloneModuleIgnoreMembers( + const Module& module, absl::Span members_to_ignore); + // Returns a CloneReplacer that runs `first` and then runs `second` on the // preliminary result, short-circuiting if `first` returns an error. CloneReplacer ChainCloneReplacers(CloneReplacer first, CloneReplacer second); diff --git a/xls/dslx/frontend/ast_cloner_test.cc b/xls/dslx/frontend/ast_cloner_test.cc index f4363ceab7..b0b4d0285a 100644 --- a/xls/dslx/frontend/ast_cloner_test.cc +++ b/xls/dslx/frontend/ast_cloner_test.cc @@ -13,6 +13,7 @@ // limitations under the License. #include "xls/dslx/frontend/ast_cloner.h" +#include #include #include #include @@ -23,12 +24,12 @@ #include #include -#include "gmock/gmock.h" -#include "gtest/gtest.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" #include "xls/common/casts.h" #include "xls/common/status/matchers.h" #include "xls/common/status/ret_check.h" @@ -46,7 +47,9 @@ namespace xls::dslx { namespace { using ::absl_testing::StatusIs; +using ::testing::HasSubstr; using ::testing::IsEmpty; +using ::testing::Not; std::optional FindFirstTypeRef(AstNode* node) { if (auto type_ref = dynamic_cast(node); type_ref) { @@ -2346,5 +2349,217 @@ fn main() -> u32 { foo::D } EXPECT_EQ(subjects[0].name_def().definer(), &subjects[0].use_tree_entry()); } +TEST(AstClonerTest, CloneModuleIgnoreMembersDropsRequestedMembers) { + constexpr std::string_view kProgram = R"( +const CONST_KEEP = u32:1; +const CONST_DROP = u32:2; + +fn main() -> u32 { + CONST_KEEP +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN( + auto module, ParseModule(kProgram, "drop.x", "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(ConstantDef * const_drop, + module->GetMemberOrError("CONST_DROP")); + const std::array ignored = {const_drop}; + XLS_ASSERT_OK_AND_ASSIGN(auto clone, + CloneModuleIgnoreMembers(*module, ignored)); + EXPECT_THAT(clone->ToString(), HasSubstr("const CONST_KEEP")); + EXPECT_THAT(clone->ToString(), Not(HasSubstr("CONST_DROP"))); + EXPECT_THAT(clone->GetConstantDef("CONST_KEEP"), + StatusIs(absl::StatusCode::kOk)); + EXPECT_THAT(clone->GetConstantDef("CONST_DROP"), + StatusIs(absl::StatusCode::kNotFound)); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersFailsOnDanglingNameRefs) { + constexpr std::string_view kProgram = R"( +const VALUE = u32:7; + +fn helper() -> u32 { + VALUE +} + +fn main() -> u32 { + helper() +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN( + auto module, ParseModule(kProgram, "dep.x", "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(Function * helper, + module->GetMemberOrError("helper")); + const std::array ignored = {helper}; + EXPECT_THAT( + CloneModuleIgnoreMembers(*module, ignored), + StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("helper"))); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersFailsOnDanglingTypeRefs) { + constexpr std::string_view kProgram = R"( +struct Foo { + x: u32, +} + +fn make() -> Foo { + Foo { x: u32:0 } +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN(auto module, ParseModule(kProgram, "type_dep.x", + "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(StructDef * foo, + module->GetMemberOrError("Foo")); + const std::array ignored = {foo}; + EXPECT_THAT(CloneModuleIgnoreMembers(*module, ignored), + StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Foo"))); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersFailsOnDanglingUseTypeRefs) { + constexpr std::string_view kProgram = R"( +#![feature(use_syntax)] +use foo::{Bar}; + +fn make(x: Bar) -> Bar { + x +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN(auto module, ParseModule(kProgram, "use_type.x", + "the_module", file_table)); + Use* use_member = nullptr; + for (const ModuleMember& member : module->top()) { + if (std::holds_alternative(member)) { + use_member = std::get(member); + break; + } + } + ASSERT_NE(use_member, nullptr); + const std::array ignored = {use_member}; + EXPECT_THAT(CloneModuleIgnoreMembers(*module, ignored), + StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Bar"))); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersRebindsNameRefs) { + constexpr std::string_view kProgram = R"( +fn helper(x: u32) -> u32 { + x + u32:1 +} + +fn main(x: u32) -> u32 { + helper(x) +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN(auto module, ParseModule(kProgram, "ref_bug.x", + "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(Function * original_helper, + module->GetMemberOrError("helper")); + const std::array ignored = {}; + XLS_ASSERT_OK_AND_ASSIGN(auto clone, + CloneModuleIgnoreMembers(*module, ignored)); + XLS_ASSERT_OK_AND_ASSIGN(Function * clone_helper, + clone->GetMemberOrError("helper")); + XLS_ASSERT_OK_AND_ASSIGN(Function * clone_main, + clone->GetMemberOrError("main")); + + std::optional maybe_ref = + FindFirstNameRefWithId(clone_main, "helper"); + ASSERT_TRUE(maybe_ref.has_value()); + NameRef* name_ref = *maybe_ref; + ASSERT_TRUE(std::holds_alternative(name_ref->name_def())); + const NameDef* ref_def = std::get(name_ref->name_def()); + + EXPECT_NE(clone_helper->name_def(), original_helper->name_def()); + EXPECT_EQ(ref_def, clone_helper->name_def()); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersSupportsMultipleIgnores) { + constexpr std::string_view kProgram = R"( +fn helper(x: u32) -> u32 { + x + u32:1 +} + +fn wrapper(x: u32) -> u32 { + helper(x) +} + +fn main(x: u32) -> u32 { + x +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN( + auto module, ParseModule(kProgram, "multi.x", "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(Function * helper, + module->GetMemberOrError("helper")); + XLS_ASSERT_OK_AND_ASSIGN(Function * wrapper, + module->GetMemberOrError("wrapper")); + const std::array ignored = {helper, wrapper}; + XLS_ASSERT_OK_AND_ASSIGN(auto clone, + CloneModuleIgnoreMembers(*module, ignored)); + // Ensure only `main` remains and it still typechecks. + EXPECT_EQ(clone->top().size(), 1); + XLS_ASSERT_OK(clone->GetMemberOrError("main")); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersRemovesStruct) { + constexpr std::string_view kProgram = R"( +struct Foo { + x: u32, +} + +fn make() -> Foo { + Foo { x: u32:0 } +} + +fn main() -> u32 { + u32:1 +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN(auto module, ParseModule(kProgram, "struct_remove.x", + "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(StructDef * foo, + module->GetMemberOrError("Foo")); + XLS_ASSERT_OK_AND_ASSIGN(Function * make_fn, + module->GetMemberOrError("make")); + const std::array ignored = {foo, make_fn}; + XLS_ASSERT_OK_AND_ASSIGN(auto clone, + CloneModuleIgnoreMembers(*module, ignored)); + EXPECT_EQ(clone->top().size(), 1); + XLS_ASSERT_OK(clone->GetMemberOrError("main")); +} + +TEST(AstClonerTest, CloneModuleIgnoreMembersFailsWhenStructReferenced) { + constexpr std::string_view kProgram = R"( +struct Foo { + x: u32, +} + +fn make() -> Foo { + Foo { x: u32:0 } +} +)"; + + FileTable file_table; + XLS_ASSERT_OK_AND_ASSIGN(auto module, ParseModule(kProgram, "struct_fail.x", + "the_module", file_table)); + XLS_ASSERT_OK_AND_ASSIGN(StructDef * foo, + module->GetMemberOrError("Foo")); + const std::array ignored = {foo}; + EXPECT_THAT(CloneModuleIgnoreMembers(*module, ignored), + StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Foo"))); +} + } // namespace } // namespace xls::dslx diff --git a/xls/dslx/frontend/module.h b/xls/dslx/frontend/module.h index 3e76fb3836..2020b42c8f 100644 --- a/xls/dslx/frontend/module.h +++ b/xls/dslx/frontend/module.h @@ -284,6 +284,9 @@ class Module : public AstNode { std::vector GetQuickCheckNames() const; const std::string& name() const { return name_; } + + void SetName(std::string name) { name_ = std::move(name); } + const std::optional& fs_path() const { return fs_path_; } diff --git a/xls/public/c_api_dslx.cc b/xls/public/c_api_dslx.cc index e8198aa742..441fe1594e 100644 --- a/xls/public/c_api_dslx.cc +++ b/xls/public/c_api_dslx.cc @@ -259,6 +259,74 @@ bool xls_dslx_parse_and_typecheck( return false; } +bool xls_dslx_typechecked_module_clone_ignore_functions( + struct xls_dslx_typechecked_module* tm, + struct xls_dslx_function* functions[], size_t function_count, + const char* install_subject, struct xls_dslx_import_data* import_data, + char** error_out, struct xls_dslx_typechecked_module** result_out) { + CHECK(error_out != nullptr); + CHECK(result_out != nullptr); + *error_out = nullptr; + *result_out = nullptr; + + if (tm == nullptr || import_data == nullptr) { + *error_out = xls::ToOwnedCString("null argument provided"); + return false; + } + + auto* cpp_tm = reinterpret_cast(tm); + auto* cpp_import_data = reinterpret_cast(import_data); + + std::string subject = std::string(install_subject); + if (subject.empty()) { + *error_out = xls::ToOwnedCString("install_subject must not be empty"); + return false; + } + + std::vector nodes_to_ignore; + nodes_to_ignore.reserve(function_count); + for (size_t i = 0; i < function_count; ++i) { + if (functions == nullptr || functions[i] == nullptr) { + *error_out = xls::ToOwnedCString("functions array contains null entry"); + return false; + } + auto* fn = reinterpret_cast(functions[i]); + if (fn->owner() != cpp_tm->module) { + *error_out = xls::ToOwnedCString( + "function does not belong to the provided module"); + return false; + } + nodes_to_ignore.push_back(fn); + } + + absl::StatusOr> cloned_module_or = + xls::dslx::CloneModuleIgnoreMembers(*cpp_tm->module, nodes_to_ignore); + if (!cloned_module_or.ok()) { + *error_out = xls::ToOwnedCString(cloned_module_or.status().ToString()); + return false; + } + + std::unique_ptr cloned_module = + std::move(cloned_module_or).value(); + if (cloned_module->name() != subject) { + cloned_module->SetName(subject); + } + std::string path = cpp_tm->module->fs_path().has_value() + ? cpp_tm->module->fs_path()->string() + : std::string(cpp_tm->module->name()); + + absl::StatusOr retyped = + xls::dslx::TypecheckModule(std::move(cloned_module), path, + cpp_import_data); + if (!retyped.ok()) { + *error_out = xls::ToOwnedCString(retyped.status().ToString()); + return false; + } + auto* new_tm = new xls::dslx::TypecheckedModule{std::move(retyped).value()}; + *result_out = reinterpret_cast(new_tm); + return true; +} + int64_t xls_dslx_module_get_member_count(struct xls_dslx_module* module) { CHECK(module != nullptr); auto* cpp_module = reinterpret_cast(module); diff --git a/xls/public/c_api_dslx.h b/xls/public/c_api_dslx.h index b23a96c838..05ed8c43d4 100644 --- a/xls/public/c_api_dslx.h +++ b/xls/public/c_api_dslx.h @@ -135,6 +135,12 @@ bool xls_dslx_parse_and_typecheck( struct xls_dslx_import_data* import_data, char** error_out, struct xls_dslx_typechecked_module** result_out); +bool xls_dslx_typechecked_module_clone_ignore_functions( + struct xls_dslx_typechecked_module* tm, + struct xls_dslx_function* functions[], size_t function_count, + const char* install_subject, struct xls_dslx_import_data* import_data, + char** error_out, struct xls_dslx_typechecked_module** result_out); + void xls_dslx_typechecked_module_free(struct xls_dslx_typechecked_module* tm); struct xls_dslx_module* xls_dslx_typechecked_module_get_module( diff --git a/xls/public/c_api_symbols.txt b/xls/public/c_api_symbols.txt index 140c41d44a..a6d337893f 100644 --- a/xls/public/c_api_symbols.txt +++ b/xls/public/c_api_symbols.txt @@ -198,6 +198,7 @@ xls_dslx_type_is_struct xls_dslx_type_ref_get_type_definition xls_dslx_type_ref_type_annotation_get_type_ref xls_dslx_type_to_string +xls_dslx_typechecked_module_clone_ignore_functions xls_dslx_typechecked_module_free xls_dslx_typechecked_module_get_module xls_dslx_typechecked_module_get_type_info diff --git a/xls/public/c_api_test.cc b/xls/public/c_api_test.cc index bada3ea9bf..4b721f8163 100644 --- a/xls/public/c_api_test.cc +++ b/xls/public/c_api_test.cc @@ -1576,6 +1576,142 @@ TEST(XlsCApiTest, DslxModuleMembers) { } } +TEST(XlsCApiTest, DslxCloneTypecheckedModuleIgnoreMembersSuccess) { + const std::string_view kProgram = R"( +fn helper(x: u32) -> u32 { + x + u32:1 +} + +fn unused() -> u32 { + helper(u32:41) +} + +fn main(x: u32) -> u32 { + x +} +)"; + + const char* additional_search_paths[] = {}; + xls_dslx_import_data* import_data = xls_dslx_import_data_create( + std::string{xls::kDefaultDslxStdlibPath}.c_str(), additional_search_paths, + 0); + ASSERT_NE(import_data, nullptr); + absl::Cleanup free_import_data( + [=] { xls_dslx_import_data_free(import_data); }); + + char* error = nullptr; + xls_dslx_typechecked_module* tm = nullptr; + bool ok = xls_dslx_parse_and_typecheck(kProgram.data(), "", "top", + import_data, &error, &tm); + ASSERT_TRUE(ok) << "error: " << error; + absl::Cleanup free_tm([=] { xls_dslx_typechecked_module_free(tm); }); + + xls_dslx_module* module = xls_dslx_typechecked_module_get_module(tm); + auto find_function = [&](std::string_view target) + -> xls_dslx_function* { + int64_t member_count = xls_dslx_module_get_member_count(module); + for (int64_t i = 0; i < member_count; ++i) { + xls_dslx_module_member* member = xls_dslx_module_get_member(module, i); + xls_dslx_function* fn = xls_dslx_module_member_get_function(member); + if (fn == nullptr) { + continue; + } + char* identifier = xls_dslx_function_get_identifier(fn); + absl::Cleanup free_identifier([&] { xls_c_str_free(identifier); }); + if (std::string_view{identifier} == target) { + return fn; + } + } + return nullptr; + }; + + xls_dslx_function* unused_fn = find_function("unused"); + ASSERT_NE(unused_fn, nullptr); + + xls_dslx_function* ignored[] = {unused_fn}; + xls_dslx_typechecked_module* cloned_tm = nullptr; + ASSERT_TRUE(xls_dslx_typechecked_module_clone_ignore_functions( + tm, ignored, ABSL_ARRAYSIZE(ignored), "top_clone", import_data, &error, + &cloned_tm)); + ASSERT_EQ(error, nullptr); + absl::Cleanup free_cloned_tm( + [=] { xls_dslx_typechecked_module_free(cloned_tm); }); + + xls_dslx_module* cloned_module = + xls_dslx_typechecked_module_get_module(cloned_tm); + EXPECT_EQ(xls_dslx_module_get_member_count(cloned_module), 2); + xls_dslx_module_member* first_member = + xls_dslx_module_get_member(cloned_module, 0); + xls_dslx_function* helper_fn = + xls_dslx_module_member_get_function(first_member); + ASSERT_NE(helper_fn, nullptr); + char* helper_name = xls_dslx_function_get_identifier(helper_fn); + absl::Cleanup free_helper_name([&] { xls_c_str_free(helper_name); }); + EXPECT_EQ(std::string_view{helper_name}, "helper"); + char* module_name = xls_dslx_module_get_name(cloned_module); + absl::Cleanup free_module_name([&] { xls_c_str_free(module_name); }); + EXPECT_EQ(std::string_view{module_name}, "top_clone"); +} + +TEST(XlsCApiTest, DslxCloneTypecheckedModuleIgnoreMembersFailure) { + const std::string_view kProgram = R"( +fn helper(x: u32) -> u32 { + x + u32:1 +} + +fn main(x: u32) -> u32 { + helper(x) +} +)"; + + const char* additional_search_paths[] = {}; + xls_dslx_import_data* import_data = xls_dslx_import_data_create( + std::string{xls::kDefaultDslxStdlibPath}.c_str(), additional_search_paths, + 0); + ASSERT_NE(import_data, nullptr); + absl::Cleanup free_import_data( + [=] { xls_dslx_import_data_free(import_data); }); + + char* error = nullptr; + xls_dslx_typechecked_module* tm = nullptr; + bool ok = xls_dslx_parse_and_typecheck(kProgram.data(), "", "top", + import_data, &error, &tm); + ASSERT_TRUE(ok) << "error: " << error; + absl::Cleanup free_tm([=] { xls_dslx_typechecked_module_free(tm); }); + + xls_dslx_module* module = xls_dslx_typechecked_module_get_module(tm); + auto find_function = [&](std::string_view target) + -> xls_dslx_function* { + int64_t member_count = xls_dslx_module_get_member_count(module); + for (int64_t i = 0; i < member_count; ++i) { + xls_dslx_module_member* member = xls_dslx_module_get_member(module, i); + xls_dslx_function* fn = xls_dslx_module_member_get_function(member); + if (fn == nullptr) { + continue; + } + char* identifier = xls_dslx_function_get_identifier(fn); + absl::Cleanup free_identifier([&] { xls_c_str_free(identifier); }); + if (std::string_view{identifier} == target) { + return fn; + } + } + return nullptr; + }; + + xls_dslx_function* helper_fn = find_function("helper"); + ASSERT_NE(helper_fn, nullptr); + + xls_dslx_function* ignored[] = {helper_fn}; + xls_dslx_typechecked_module* cloned_tm = nullptr; + EXPECT_FALSE(xls_dslx_typechecked_module_clone_ignore_functions( + tm, ignored, ABSL_ARRAYSIZE(ignored), "top", import_data, &error, + &cloned_tm)); + EXPECT_NE(error, nullptr); + absl::Cleanup free_error([&] { xls_c_str_free(error); }); + EXPECT_TRUE(std::string_view{error}.find("helper") != std::string::npos); + EXPECT_EQ(cloned_tm, nullptr); +} + TEST(XlsCApiTest, ValueGetElementCount) { const std::initializer_list< std::pair>>