// Tests for expr_typer.zig — focused on the structural (non-call) expression
// shapes ExprTyper owns, reached via the public `Lowering.inferExprType`
// delegation. These cases need no lexical scope / program-index state, so a
// bare `Lowering.init` suffices.

const std = @import("std");
const ast = @import("../ast.zig");
const Node = ast.Node;

const ir_mod = @import("ir.zig");
const TypeId = ir_mod.TypeId;
const Lowering = ir_mod.Lowering;

fn node(data: ast.Node.Data) Node {
    return .{ .span = .{ .start = 0, .end = 0 }, .data = data };
}

test "expr_typer: literal shapes" {
    const alloc = std.testing.allocator;
    var module = ir_mod.Module.init(alloc);
    defer module.deinit();
    var l = Lowering.init(&module);

    var int_n = node(.{ .int_literal = .{ .value = 7 } });
    var float_n = node(.{ .float_literal = .{ .value = 1.5 } });
    var bool_n = node(.{ .bool_literal = .{ .value = true } });
    var str_n = node(.{ .string_literal = .{ .raw = "hi" } });

    try std.testing.expectEqual(TypeId.s64, l.inferExprType(&int_n));
    try std.testing.expectEqual(TypeId.f64, l.inferExprType(&float_n));
    try std.testing.expectEqual(TypeId.bool, l.inferExprType(&bool_n));
    try std.testing.expectEqual(TypeId.string, l.inferExprType(&str_n));
}

test "expr_typer: binary comparison is bool, arithmetic takes lhs type" {
    const alloc = std.testing.allocator;
    var module = ir_mod.Module.init(alloc);
    defer module.deinit();
    var l = Lowering.init(&module);

    var lhs = node(.{ .int_literal = .{ .value = 1 } });
    var rhs = node(.{ .int_literal = .{ .value = 2 } });

    var cmp = node(.{ .binary_op = .{ .op = .eq, .lhs = &lhs, .rhs = &rhs } });
    try std.testing.expectEqual(TypeId.bool, l.inferExprType(&cmp));

    var add = node(.{ .binary_op = .{ .op = .add, .lhs = &lhs, .rhs = &rhs } });
    try std.testing.expectEqual(TypeId.s64, l.inferExprType(&add));
}

test "expr_typer: unary not is bool, negate preserves operand type" {
    const alloc = std.testing.allocator;
    var module = ir_mod.Module.init(alloc);
    defer module.deinit();
    var l = Lowering.init(&module);

    var b = node(.{ .bool_literal = .{ .value = false } });
    var not_n = node(.{ .unary_op = .{ .op = .not, .operand = &b } });
    try std.testing.expectEqual(TypeId.bool, l.inferExprType(&not_n));

    var f = node(.{ .float_literal = .{ .value = 2.0 } });
    var neg_n = node(.{ .unary_op = .{ .op = .negate, .operand = &f } });
    try std.testing.expectEqual(TypeId.f64, l.inferExprType(&neg_n));
}

test "expr_typer: deref of a non-pointer is unresolved" {
    const alloc = std.testing.allocator;
    var module = ir_mod.Module.init(alloc);
    defer module.deinit();
    var l = Lowering.init(&module);

    var i = node(.{ .int_literal = .{ .value = 0 } });
    var deref_n = node(.{ .deref_expr = .{ .operand = &i } });
    try std.testing.expectEqual(TypeId.unresolved, l.inferExprType(&deref_n));
}
