#include #include #include #include #include "fold/ir/access_path.hpp" namespace { fold::ir::Symbol synthSymbol(std::size_t id) { static const std::array slots{}; return fold::ir::Symbol{static_cast(&slots.at(id))}; } } // namespace TEST_CASE("AccessPath local identity uses symbol, frame, and steps", "[ir][access-path]") { auto sym = synthSymbol(1); auto lhs = fold::ir::AccessPath::local(sym, "value", "fn").field("total_"); auto rhs = fold::ir::AccessPath::local(sym, "value", "fn").field("total_"); auto different_field = fold::ir::AccessPath::local(sym, "value", "fn").field("done_"); auto different_frame = fold::ir::AccessPath::local(sym, "value", "other").field("total_"); REQUIRE(lhs == rhs); REQUIRE(lhs != different_field); REQUIRE(lhs != different_frame); REQUIRE(lhs.valid()); } TEST_CASE("AccessPath represents implicit this fields", "[ir][access-path][this]") { auto total = fold::ir::AccessPath::thisObject("Widget::rate").field("total_"); auto total_again = fold::ir::AccessPath::thisObject("Widget::rate").field("total_"); auto other_method = fold::ir::AccessPath::thisObject("Widget::other").field("total_"); REQUIRE(total == total_again); REQUIRE(total != other_method); CHECK(total.canonicalName().find("this") != std::string::npos); CHECK(total.canonicalName().find("field(total_)") != std::string::npos); } TEST_CASE("AccessPath can model pointer, element, and member chains", "[ir][access-path]") { auto base = fold::ir::AccessPath::local(synthSymbol(2), "ptr", "fn"); auto chain = base.deref().field("items").element("i").field("size"); auto same = base.deref().field("items").element("i").field("size"); auto different_index = base.deref().field("items").element("j").field("size"); REQUIRE(chain == same); REQUIRE(chain != different_index); CHECK(chain.canonicalName().find("deref") != std::string::npos); CHECK(chain.canonicalName().find("element(i)") != std::string::npos); } TEST_CASE("AccessPath stable query calls are first-class steps", "[ir][access-path][query]") { auto base = fold::ir::AccessPath::local(synthSymbol(3), "sections", "fn"); auto size = base.stableQuery(fold::ir::AccessKind::Size); auto size_again = base.stableQuery(fold::ir::AccessKind::Size); auto empty = base.stableQuery(fold::ir::AccessKind::Empty); REQUIRE(size == size_again); REQUIRE(size != empty); REQUIRE(fold::ir::isStableQueryAccess(fold::ir::AccessKind::Size)); REQUIRE_FALSE(fold::ir::isStableQueryAccess(fold::ir::AccessKind::Field)); CHECK(size.canonicalName().find("size") != std::string::npos); } TEST_CASE("AccessPath hashes are compatible with equality", "[ir][access-path][hash]") { using Map = std::unordered_map; auto base = fold::ir::AccessPath::local(synthSymbol(4), "ctx", "write_cb"); auto ctx_total = base.field("total"); auto ctx_total_again = base.field("total"); Map values; values.emplace(ctx_total, 7); REQUIRE(values.at(ctx_total_again) == 7); } TEST_CASE("AccessPath allocation and synthetic regions remain distinct", "[ir][access-path]") { auto allocation = fold::ir::AccessPath::allocationSite("new@10", "fn"); auto synthetic = fold::ir::AccessPath::synthetic("new@10", "fn"); REQUIRE(allocation != synthetic); CHECK(allocation.canonicalName().find("alloc") != std::string::npos); CHECK(synthetic.canonicalName().find("synthetic") != std::string::npos); } TEST_CASE("AccessPath descendant matching is base and prefix aware", "[ir][access-path][havoc]") { auto base = fold::ir::AccessPath::local(synthSymbol(5), "doc", "fn"); auto sections = base.field("sections"); auto size = sections.stableQuery(fold::ir::AccessKind::Size); auto other_field = base.field("title"); auto other_base = fold::ir::AccessPath::local(synthSymbol(6), "doc", "fn").field("sections"); CHECK(fold::ir::isSameOrDescendantAccessPath(base, base)); CHECK(fold::ir::isSameOrDescendantAccessPath(sections, base)); CHECK(fold::ir::isSameOrDescendantAccessPath(size, base)); CHECK(fold::ir::isSameOrDescendantAccessPath(size, sections)); CHECK_FALSE(fold::ir::isSameOrDescendantAccessPath(other_field, sections)); CHECK_FALSE(fold::ir::isSameOrDescendantAccessPath(other_base, base)); }