use crate::test_utils::{compile, eval};
#[test]
fn pop_mutation_deque_pop_back_returns_element_and_writes_back() {
let popped = eval(
r#"
let mut d = Deque()
d.pushBack("a")
d.pushBack("b")
d.popBack()
"#,
);
assert_eq!(popped.as_str(), Some("b"));
}
#[test]
fn pop_mutation_deque_pop_back_shrinks_size() {
let size_after = eval(
r#"
let mut d = Deque()
d.pushBack("a")
d.pushBack("b")
d.popBack()
d.size()
"#,
);
assert_eq!(size_after.as_i64(), Some(1));
}
#[test]
fn pop_mutation_deque_pop_front_returns_first_and_writes_back() {
let popped = eval(
r#"
let mut d = Deque()
d.pushBack("a")
d.pushBack("b")
d.popFront()
"#,
);
assert_eq!(popped.as_str(), Some("a"));
}
#[test]
fn pop_mutation_deque_pop_front_shrinks_size() {
let size_after = eval(
r#"
let mut d = Deque()
d.pushBack("a")
d.pushBack("b")
d.popFront()
d.size()
"#,
);
assert_eq!(size_after.as_i64(), Some(1));
}
#[test]
fn pop_mutation_deque_pop_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let d = Deque()
d.pushBack("a")
d.popBack()
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for Deque.popBack on immutable binding"
);
}
#[test]
fn pop_mutation_priority_queue_pop_returns_min() {
let popped = eval(
r#"
let mut q = PriorityQueue()
q.push(3)
q.push(1)
q.push(2)
q.pop()
"#,
);
assert_eq!(popped.as_i64(), Some(1));
}
#[test]
fn pop_mutation_priority_queue_pop_shrinks_size() {
let size_after = eval(
r#"
let mut q = PriorityQueue()
q.push(3)
q.push(1)
q.push(2)
q.pop()
q.size()
"#,
);
assert_eq!(size_after.as_i64(), Some(2));
}
#[test]
fn pop_mutation_priority_queue_pop_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let q = PriorityQueue()
q.push(1)
q.pop()
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for PriorityQueue.pop on immutable binding"
);
}
#[test]
fn pop_mutation_hashmap_remove_returns_value() {
let popped = eval(
r#"
let mut m = HashMap()
m.set("a", "first")
m.set("b", "second")
m.remove("a")
"#,
);
assert_eq!(popped.as_str(), Some("first"));
}
#[test]
fn pop_mutation_hashmap_remove_shrinks_size() {
let size_after = eval(
r#"
let mut m = HashMap()
m.set("a", "first")
m.set("b", "second")
m.remove("a")
m.len()
"#,
);
assert_eq!(size_after.as_i64(), Some(1));
}
#[test]
fn pop_mutation_hashmap_remove_missing_key_returns_none() {
let size_after = eval(
r#"
let mut m = HashMap()
m.set("a", "first")
m.set("b", "second")
m.remove("zzz")
m.len()
"#,
);
assert_eq!(size_after.as_i64(), Some(2));
}
#[test]
fn pop_mutation_hashmap_remove_let_immutable_compile_error() {
let program = shape_ast::parser::parse_program(
r#"
let m = HashMap()
m.set("a", "x")
m.remove("a")
"#,
)
.expect("parse should succeed");
let compiler = crate::compiler::BytecodeCompiler::new();
let result = compiler.compile(&program);
assert!(
result.is_err(),
"expected compile error for HashMap.remove on immutable binding"
);
}
#[test]
fn pop_mutation_hashmap_delete_still_returns_self_for_set_wrapper() {
let result = eval(
r#"
let mut m = HashMap()
m.set("a", "x")
m.set("b", "y")
m.delete("a")
m.len()
"#,
);
assert_eq!(result.as_i64(), Some(1));
}
#[test]
fn pop_mutation_rvalue_receiver_silent_drops_new_container() {
let result = eval(
r#"
fn make_deque() -> Deque {
let mut d = Deque()
d.pushBack("a")
d.pushBack("b")
d.pushBack("c")
return d
}
let _ = make_deque().popBack()
42
"#,
);
assert_eq!(result.as_i64(), Some(42));
}
#[test]
fn pop_mutation_rvalue_receiver_returns_popped_element() {
let result = eval(
r#"
fn make_pq() -> PriorityQueue {
let mut q = PriorityQueue()
q.push(5)
q.push(2)
q.push(8)
return q
}
make_pq().pop()
"#,
);
assert_eq!(result.as_i64(), Some(2));
}
#[test]
fn pop_mutation_emits_swap_store_on_pop_method() {
let bc = compile(
r#"
let mut d = Deque()
d.pushBack("a")
d.popBack()
"#,
);
use crate::bytecode::OpCode;
let top = &bc.instructions;
let mut saw_swap_store = 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::Swap
&& matches!(
top[i + 2].opcode,
OpCode::StoreLocal | OpCode::StoreModuleBinding
)
{
saw_swap_store = true;
break;
}
}
assert!(
saw_swap_store,
"expected `CallMethod; Swap; Store{{Local,ModuleBinding}}` tuple-return writeback sequence:\n{}",
diag
);
}
#[test]
fn pop_mutation_rvalue_emits_swap_pop_silent_drop() {
let bc = compile(
r#"
fn mk() -> Deque {
let mut d = Deque()
d.pushBack("a")
return d
}
mk().popBack()
"#,
);
use crate::bytecode::OpCode;
let top = &bc.instructions;
let mut saw_swap_pop = 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::Swap
&& top[i + 2].opcode == OpCode::Pop
{
saw_swap_pop = true;
break;
}
}
assert!(
saw_swap_pop,
"expected `CallMethod; Swap; Pop` silent-drop sequence at r-value popBack:\n{}",
diag
);
}