use crate::test_utils::{compile, eval, eval_with_kind};
use shape_value::NativeKind;
#[test]
fn writeback_hashset_add_size() {
let result = eval(
r#"
let mut s = Set()
s.add("a")
s.add("b")
s.size()
"#,
);
assert_eq!(result.as_i64(), Some(2));
}
#[test]
fn writeback_hashset_add_duplicate_is_idempotent() {
let result = eval(
r#"
let mut s = Set()
s.add("a")
s.add("a")
s.size()
"#,
);
assert_eq!(result.as_i64(), Some(1));
}
#[test]
fn writeback_hashset_delete() {
let result = eval(
r#"
let mut s = Set()
s.add("a")
s.add("b")
s.delete("a")
s.size()
"#,
);
assert_eq!(result.as_i64(), Some(1));
}
#[test]
fn writeback_hashset_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let s = Set()
s.add("x")
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for mutation on immutable binding, got Ok"
);
let err_msg = format!("{:?}", result.err().unwrap());
assert!(
err_msg.contains("immutable") || err_msg.contains("let mut"),
"expected `immutable` / `let mut` diagnostic, got: {}",
err_msg
);
}
#[test]
fn writeback_hashmap_set_get() {
let result = eval(
r#"
let mut m = HashMap()
m.set("a", 1)
m.set("b", 2)
m.len()
"#,
);
assert_eq!(result.as_i64(), Some(2));
}
#[test]
fn writeback_hashmap_delete() {
let result = eval(
r#"
let mut m = HashMap()
m.set("a", 1)
m.set("b", 2)
m.delete("a")
m.len()
"#,
);
assert_eq!(result.as_i64(), Some(1));
}
#[test]
fn writeback_hashmap_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let m = HashMap()
m.set("a", 1)
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for HashMap.set on immutable binding"
);
}
#[test]
fn writeback_deque_push_back_then_size() {
let result = eval(
r#"
let mut d = Deque()
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
d.size()
"#,
);
assert_eq!(result.as_i64(), Some(3));
}
#[test]
fn writeback_deque_push_front_then_size() {
let result = eval(
r#"
let mut d = Deque()
d.pushFront(1)
d.pushFront(2)
d.size()
"#,
);
assert_eq!(result.as_i64(), Some(2));
}
#[test]
fn writeback_deque_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let d = Deque()
d.pushBack(1)
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for Deque.pushBack on immutable binding"
);
}
#[test]
fn writeback_priority_queue_push_pop() {
let result = eval(
r#"
let mut q = PriorityQueue()
q.push(3)
q.push(1)
q.push(2)
q.size()
"#,
);
assert_eq!(result.as_i64(), Some(3));
}
#[test]
fn writeback_priority_queue_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let q = PriorityQueue()
q.push(1)
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for PriorityQueue.push on immutable binding"
);
}
#[test]
fn writeback_rvalue_receiver_silent_drops() {
let _ = eval(
r#"
Set().add("x")
42
"#,
);
}
#[test]
fn compound_assign_int_addition() {
let result = eval(
r#"
let mut n = 5
n += 3
n
"#,
);
assert_eq!(result.as_i64(), Some(8));
}
#[test]
fn compound_assign_int_subtraction() {
let result = eval(
r#"
let mut n = 10
n -= 4
n
"#,
);
assert_eq!(result.as_i64(), Some(6));
}
#[test]
fn compound_assign_int_multiplication() {
let result = eval(
r#"
let mut n = 3
n *= 4
n
"#,
);
assert_eq!(result.as_i64(), Some(12));
}
#[test]
fn compound_assign_number_addition() {
let result = eval_with_kind(
r#"
let mut n: number = 1.5
n += 2.25
n
"#,
NativeKind::Float64,
);
assert_eq!(result.as_f64(), Some(3.75));
}
#[test]
fn compound_assign_string_concatenation() {
let result = eval(
r#"
let mut s = "hi"
s += " there"
s
"#,
);
assert_eq!(result.as_str().as_deref(), Some("hi there"));
}
#[test]
fn binary_op_without_assignment_is_pure() {
let result = eval(
r#"
let mut s = 5
let t = s + 3
s
"#,
);
assert_eq!(result.as_i64(), Some(5));
}
#[test]
fn binary_op_without_assignment_returns_new_value() {
let result = eval(
r#"
let mut s = 5
let t = s + 3
t
"#,
);
assert_eq!(result.as_i64(), Some(8));
}
#[test]
fn mutex_set_on_let_immutable_binding_works() {
let result = eval(
r#"
let m = Mutex(0)
m.set(5)
m.get()
"#,
);
assert_eq!(result.as_i64(), Some(5));
}
#[test]
fn atomic_store_on_let_immutable_binding_works() {
let result = eval(
r#"
let a = Atomic(0)
a.store(7)
a.load()
"#,
);
assert_eq!(result.as_i64(), Some(7));
}
#[test]
fn widening_i8_to_i32_via_compound_assign() {
let result = eval_with_kind(
r#"
let mut n: i32 = 0
let x: i8 = 5
n += x
n
"#,
NativeKind::Int32,
);
let bits = result.raw();
let val = (bits & 0xFFFF_FFFF) as i32;
assert_eq!(val, 5);
}
#[test]
fn widening_i16_to_i64_in_binding() {
let result = eval(
r#"
let x: i16 = 1234
let n: i64 = x
n
"#,
);
assert_eq!(result.as_i64(), Some(1234));
}
#[test]
fn widening_u8_to_u16_in_binding() {
let result = eval(
r#"
let x: u8 = 200
let n: u16 = x
n
"#,
);
assert_eq!(result.as_i64(), Some(200));
}
#[test]
fn widening_u8_to_u32_in_binding() {
let result = eval(
r#"
let x: u8 = 255
let n: u32 = x
n
"#,
);
assert_eq!(result.as_i64(), Some(255));
}
#[test]
fn writeback_emits_dup_storelocal_on_mut_method() {
let bc = compile(
r#"
let mut s = Set()
s.add("a")
s.size()
"#,
);
use crate::bytecode::OpCode;
let top = &bc.instructions;
let mut saw_dup_after_call = false;
let mut diag = String::new();
for (i, ins) in top.iter().enumerate() {
diag.push_str(&format!("{:3}: {:?}\n", i, ins.opcode));
if ins.opcode == OpCode::CallMethod
&& i + 2 < top.len()
&& top[i + 1].opcode == OpCode::Dup
&& matches!(top[i + 2].opcode, OpCode::StoreLocal | OpCode::StoreModuleBinding)
{
saw_dup_after_call = true;
break;
}
}
assert!(
saw_dup_after_call,
"expected `CallMethod; Dup; Store{{Local,ModuleBinding}}` writeback sequence in bytecode:\n{}",
diag
);
}