fold/tests/unit/test_access_path.cpp
2026-06-04 20:20:29 -04:00

100 lines
4.4 KiB
C++

#include <array>
#include <catch2/catch_test_macros.hpp>
#include <string>
#include <unordered_map>
#include "fold/ir/access_path.hpp"
namespace {
fold::ir::Symbol synthSymbol(std::size_t id) {
static const std::array<int, 16> slots{};
return fold::ir::Symbol{static_cast<const void*>(&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<fold::ir::AccessPath, int, fold::ir::AccessPathHash>;
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));
}