- Language enum: Unknown, Rust, Cpp, Swift on RustExprNode - 4 new Clang node kinds: ThrowExpr, CXXConstructExpr, LambdaExpr, StmtExpr - Updated json_keys namespace with new constants - 42 roundtrip test assertions pass - All 3 frontends now share the same IR representation
366 lines
12 KiB
C++
366 lines
12 KiB
C++
// RF-2: wire-format round-trip test for fold::ir::Expr.
|
|
//
|
|
// Build a RustExprNode, serialize to JSON, deserialize back, assert
|
|
// structural equality. Exercises every Expr::Kind the RF-2 schema
|
|
// supports.
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include "fold/ir/expr.hpp"
|
|
#include "fold/ir/expr_json.hpp"
|
|
#include "fold/ir/opcode.hpp"
|
|
#include "fold/ir/source_loc.hpp"
|
|
|
|
using fold::ir::BinaryOpcode;
|
|
using fold::ir::Expr;
|
|
using fold::ir::fromJson;
|
|
using fold::ir::RustExprNode;
|
|
using fold::ir::RustNodeArena;
|
|
using fold::ir::SourceLoc;
|
|
using fold::ir::toJson;
|
|
using fold::ir::UnaryOpcode;
|
|
|
|
namespace {
|
|
|
|
// Structural equality, ignoring distinct pointer identity. Two nodes
|
|
// match when their kind + payload + recursively their children match.
|
|
bool nodesEqual(const RustExprNode* a, const RustExprNode* b) {
|
|
if (a == nullptr || b == nullptr)
|
|
return a == b;
|
|
if (a->kind != b->kind)
|
|
return false;
|
|
if (a->language != b->language)
|
|
return false;
|
|
if (a->loc.file != b->loc.file || a->loc.line != b->loc.line || a->loc.column != b->loc.column)
|
|
return false;
|
|
switch (a->kind) {
|
|
case Expr::Kind::DeclRef:
|
|
return a->declref_symbol == b->declref_symbol;
|
|
case Expr::Kind::IntegerLiteral:
|
|
return a->int_value == b->int_value && a->int_width == b->int_width &&
|
|
a->int_signed == b->int_signed;
|
|
case Expr::Kind::BoolLiteral:
|
|
return a->bool_value == b->bool_value;
|
|
case Expr::Kind::NullPtr:
|
|
return true;
|
|
case Expr::Kind::BinaryOp:
|
|
return a->binary_op == b->binary_op && nodesEqual(a->binary_lhs, b->binary_lhs) &&
|
|
nodesEqual(a->binary_rhs, b->binary_rhs);
|
|
case Expr::Kind::UnaryOp:
|
|
case Expr::Kind::AddrOf:
|
|
case Expr::Kind::Deref:
|
|
case Expr::Kind::Cast:
|
|
return a->unary_op == b->unary_op && nodesEqual(a->unary_sub, b->unary_sub);
|
|
case Expr::Kind::Call:
|
|
case Expr::Kind::MemberCall:
|
|
case Expr::Kind::ConstructorCall: {
|
|
if (a->callee_name != b->callee_name || a->call_args.size() != b->call_args.size())
|
|
return false;
|
|
for (std::size_t i = 0; i < a->call_args.size(); ++i)
|
|
if (!nodesEqual(a->call_args[i], b->call_args[i]))
|
|
return false;
|
|
return true;
|
|
}
|
|
case Expr::Kind::Member: {
|
|
return a->member_field == b->member_field && nodesEqual(a->member_base, b->member_base);
|
|
}
|
|
case Expr::Kind::ArraySubscript:
|
|
return nodesEqual(a->array_base, b->array_base) &&
|
|
nodesEqual(a->array_index, b->array_index);
|
|
case Expr::Kind::HeapNew:
|
|
case Expr::Kind::HeapDelete:
|
|
return a->heap_is_array == b->heap_is_array &&
|
|
nodesEqual(a->heap_alloc_arg, b->heap_alloc_arg);
|
|
case Expr::Kind::ThrowExpr:
|
|
return nodesEqual(a->throw_sub, b->throw_sub);
|
|
case Expr::Kind::CXXConstructExpr:
|
|
if (a->callee_name != b->callee_name || a->call_args.size() != b->call_args.size())
|
|
return false;
|
|
for (std::size_t i = 0; i < a->call_args.size(); ++i)
|
|
if (!nodesEqual(a->call_args[i], b->call_args[i]))
|
|
return false;
|
|
return true;
|
|
case Expr::Kind::LambdaExpr:
|
|
return nodesEqual(a->lambda_body, b->lambda_body);
|
|
case Expr::Kind::StmtExpr:
|
|
return nodesEqual(a->stmt_expr_body, b->stmt_expr_body);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Round-trip helper: serialize → deserialize → compare.
|
|
void requireRoundTrip(const RustExprNode& original) {
|
|
auto j = toJson(original);
|
|
RustNodeArena arena;
|
|
auto* parsed = fromJson(j, arena);
|
|
REQUIRE(parsed != nullptr);
|
|
REQUIRE(nodesEqual(parsed, &original));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("expr_json: DeclRef round-trip", "[expr_json][roundtrip]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::DeclRef;
|
|
node.loc = SourceLoc{"foo.rs", 12, 4};
|
|
node.declref_symbol = "crate::foo::x";
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: IntegerLiteral round-trip", "[expr_json][roundtrip]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::IntegerLiteral;
|
|
node.loc = SourceLoc{"foo.rs", 3, 7};
|
|
node.int_value = -42;
|
|
node.int_width = 32;
|
|
node.int_signed = true;
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: BoolLiteral round-trip", "[expr_json][roundtrip]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::BoolLiteral;
|
|
node.loc = SourceLoc{"foo.rs", 1, 1};
|
|
node.bool_value = true;
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: NullPtr round-trip", "[expr_json][roundtrip]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::NullPtr;
|
|
node.loc = SourceLoc{"foo.rs", 9, 2};
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: BinaryOp Add of two literals round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* lhs = scratch.alloc();
|
|
lhs->kind = Expr::Kind::IntegerLiteral;
|
|
lhs->int_value = 7;
|
|
lhs->int_width = 32;
|
|
auto* rhs = scratch.alloc();
|
|
rhs->kind = Expr::Kind::DeclRef;
|
|
rhs->declref_symbol = "crate::y";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::BinaryOp;
|
|
root.loc = SourceLoc{"foo.rs", 5, 9};
|
|
root.binary_op = BinaryOpcode::Add;
|
|
root.binary_lhs = lhs;
|
|
root.binary_rhs = rhs;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: Deref of DeclRef round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* sub = scratch.alloc();
|
|
sub->kind = Expr::Kind::DeclRef;
|
|
sub->declref_symbol = "crate::p";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::Deref;
|
|
root.loc = SourceLoc{"foo.rs", 11, 13};
|
|
root.unary_op = UnaryOpcode::Deref;
|
|
root.unary_sub = sub;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: AddrOf of DeclRef round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* sub = scratch.alloc();
|
|
sub->kind = Expr::Kind::DeclRef;
|
|
sub->declref_symbol = "crate::q";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::AddrOf;
|
|
root.unary_op = UnaryOpcode::AddrOf;
|
|
root.unary_sub = sub;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: Call of DeclRef round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* arg = scratch.alloc();
|
|
arg->kind = Expr::Kind::IntegerLiteral;
|
|
arg->int_value = 42;
|
|
arg->int_width = 32;
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::Call;
|
|
root.loc = SourceLoc{"foo.rs", 10, 5};
|
|
root.callee_name = "my_func";
|
|
root.call_args = {arg};
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: Member access round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* base = scratch.alloc();
|
|
base->kind = Expr::Kind::DeclRef;
|
|
base->declref_symbol = "crate::obj";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::Member;
|
|
root.loc = SourceLoc{"foo.rs", 7, 3};
|
|
root.member_base = base;
|
|
root.member_field = "size";
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: ArraySubscript round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* base = scratch.alloc();
|
|
base->kind = Expr::Kind::DeclRef;
|
|
base->declref_symbol = "crate::arr";
|
|
auto* idx = scratch.alloc();
|
|
idx->kind = Expr::Kind::IntegerLiteral;
|
|
idx->int_value = 0;
|
|
idx->int_width = 64;
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::ArraySubscript;
|
|
root.loc = SourceLoc{"foo.rs", 8, 4};
|
|
root.array_base = base;
|
|
root.array_index = idx;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: Cast round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* sub = scratch.alloc();
|
|
sub->kind = Expr::Kind::DeclRef;
|
|
sub->declref_symbol = "crate::x";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::Cast;
|
|
root.loc = SourceLoc{"foo.rs", 6, 2};
|
|
root.unary_sub = sub;
|
|
root.cast_type_name = "i32";
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: HeapNew round-trip", "[expr_json][roundtrip]") {
|
|
RustNodeArena scratch;
|
|
auto* arg = scratch.alloc();
|
|
arg->kind = Expr::Kind::IntegerLiteral;
|
|
arg->int_value = 16;
|
|
arg->int_width = 64;
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::HeapNew;
|
|
root.loc = SourceLoc{"foo.rs", 15, 3};
|
|
root.heap_alloc_arg = arg;
|
|
root.heap_is_array = true;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: language field round-trip (Rust)", "[expr_json][roundtrip][language]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::DeclRef;
|
|
node.language = fold::ir::Language::Rust;
|
|
node.declref_symbol = "crate::x";
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: language field round-trip (Cpp)", "[expr_json][roundtrip][language]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::DeclRef;
|
|
node.language = fold::ir::Language::Cpp;
|
|
node.declref_symbol = "::my_var";
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: language field round-trip (Swift)", "[expr_json][roundtrip][language]") {
|
|
RustExprNode node;
|
|
node.kind = Expr::Kind::DeclRef;
|
|
node.language = fold::ir::Language::Swift;
|
|
node.declref_symbol = "myVar";
|
|
requireRoundTrip(node);
|
|
}
|
|
|
|
TEST_CASE("expr_json: language field defaults to Unknown", "[expr_json][roundtrip][language]") {
|
|
RustNodeArena arena;
|
|
nlohmann::json j;
|
|
j["kind"] = "DeclRef";
|
|
j["symbol"] = "x";
|
|
// No language key -- should default to Unknown.
|
|
auto* parsed = fromJson(j, arena);
|
|
REQUIRE(parsed != nullptr);
|
|
REQUIRE(parsed->language == fold::ir::Language::Unknown);
|
|
}
|
|
|
|
TEST_CASE("expr_json: ThrowExpr round-trip", "[expr_json][roundtrip][clang]") {
|
|
RustNodeArena scratch;
|
|
auto* sub = scratch.alloc();
|
|
sub->kind = Expr::Kind::DeclRef;
|
|
sub->declref_symbol = "std::runtime_error";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::ThrowExpr;
|
|
root.language = fold::ir::Language::Cpp;
|
|
root.loc = SourceLoc{"throw.cpp", 42, 5};
|
|
root.throw_sub = sub;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: CXXConstructExpr round-trip", "[expr_json][roundtrip][clang]") {
|
|
RustNodeArena scratch;
|
|
auto* arg = scratch.alloc();
|
|
arg->kind = Expr::Kind::IntegerLiteral;
|
|
arg->int_value = 42;
|
|
arg->int_width = 32;
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::CXXConstructExpr;
|
|
root.language = fold::ir::Language::Cpp;
|
|
root.loc = SourceLoc{"construct.cpp", 10, 3};
|
|
root.callee_name = "std::string";
|
|
root.call_args = {arg};
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: LambdaExpr round-trip", "[expr_json][roundtrip][clang]") {
|
|
RustNodeArena scratch;
|
|
auto* body = scratch.alloc();
|
|
body->kind = Expr::Kind::DeclRef;
|
|
body->declref_symbol = "captured_x";
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::LambdaExpr;
|
|
root.language = fold::ir::Language::Cpp;
|
|
root.loc = SourceLoc{"lambda.cpp", 7, 12};
|
|
root.lambda_body = body;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: StmtExpr round-trip", "[expr_json][roundtrip][clang]") {
|
|
RustNodeArena scratch;
|
|
auto* body = scratch.alloc();
|
|
body->kind = Expr::Kind::IntegerLiteral;
|
|
body->int_value = 1;
|
|
body->int_width = 32;
|
|
|
|
RustExprNode root;
|
|
root.kind = Expr::Kind::StmtExpr;
|
|
root.language = fold::ir::Language::Cpp;
|
|
root.loc = SourceLoc{"stmtexpr.c", 15, 3};
|
|
root.stmt_expr_body = body;
|
|
requireRoundTrip(root);
|
|
}
|
|
|
|
TEST_CASE("expr_json: unknown kind decodes to nullptr", "[expr_json][roundtrip][error]") {
|
|
nlohmann::json j;
|
|
j["kind"] = "SomethingFromTheFuture";
|
|
RustNodeArena arena;
|
|
REQUIRE(fromJson(j, arena) == nullptr);
|
|
}
|
|
|
|
TEST_CASE("expr_json: malformed input (no kind) decodes to nullptr",
|
|
"[expr_json][roundtrip][error]") {
|
|
nlohmann::json j;
|
|
j["op"] = "Add";
|
|
RustNodeArena arena;
|
|
REQUIRE(fromJson(j, arena) == nullptr);
|
|
}
|