use std::sync::Arc;
use harn_parser::TypeExpr;
use super::{
Chunk, CompiledFunction, Constant, DirectCallState, DirectCallTarget, InlineCacheEntry,
MethodCacheTarget, Op, ParamSlot, PropertyCacheTarget,
};
use crate::BuiltinId;
fn named_list_element(type_expr: &Option<TypeExpr>) -> &str {
match type_expr {
Some(TypeExpr::List(inner)) => match inner.as_ref() {
TypeExpr::Named(name) => name,
other => panic!("expected named list element, got {other:?}"),
},
other => panic!("expected list parameter type, got {other:?}"),
}
}
#[test]
fn op_from_byte_matches_repr_order() {
for (byte, op) in Op::ALL.iter().copied().enumerate() {
assert_eq!(byte as u8, op as u8);
assert_eq!(Op::from_byte(byte as u8), Some(op));
}
assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
assert_eq!(Op::COUNT, Op::ALL.len());
}
#[test]
fn disassemble_covers_every_opcode_variant() {
for op in Op::ALL.iter().copied() {
let mut chunk = Chunk::new();
chunk.add_constant(super::Constant::String("__probe__".to_string()));
for _ in 0..16 {
chunk.code.push(0);
}
let mut ip: usize = 0;
let mut out = String::new();
chunk.disassemble_op(op, &mut ip, &mut out);
assert!(
!out.contains("UNKNOWN"),
"disasm emitted UNKNOWN for {op:?}: {out}",
);
assert!(!out.is_empty(), "disasm produced no output for {op:?}");
}
}
#[test]
fn empty_chunk_does_not_reference_outer_names() {
let chunk = Chunk::new();
assert!(!chunk.references_outer_names);
}
#[test]
fn arithmetic_only_chunk_does_not_reference_outer_names() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetLocalSlot, 0, 1);
chunk.emit_u16(Op::Constant, 0, 1);
chunk.emit(Op::MulInt, 1);
chunk.emit(Op::Pop, 1);
chunk.emit(Op::Return, 1);
assert!(!chunk.references_outer_names);
}
#[test]
fn slot_only_chunk_does_not_reference_outer_names() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::DefLocalSlot, 0, 1);
chunk.emit_u16(Op::GetLocalSlot, 0, 1);
chunk.emit_u16(Op::SetLocalSlot, 0, 1);
assert!(!chunk.references_outer_names);
}
#[test]
fn get_var_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetVar, 0, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn set_var_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::SetVar, 0, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn check_type_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::CheckType, 0, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn call_builtin_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_call_builtin(BuiltinId::from_name("any_name"), 0, 1, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn call_builtin_spread_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_call_builtin_spread(BuiltinId::from_name("any_name"), 0, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn tail_call_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::TailCall, 1, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn call_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::Call, 1, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn pipe_flags_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit(Op::Pipe, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn method_call_does_not_flag_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_method_call(0, 1, 1);
chunk.emit_method_call_opt(0, 1, 1);
assert!(!chunk.references_outer_names);
}
#[test]
fn jump_and_control_flow_do_not_flag_outer_name_reference() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::Constant, 0, 1);
chunk.emit(Op::JumpIfFalse, 1);
chunk.emit(Op::Jump, 1);
chunk.emit(Op::Return, 1);
chunk.emit(Op::Pop, 1);
assert!(!chunk.references_outer_names);
}
#[test]
fn references_outer_names_is_monotonic() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetVar, 0, 1);
assert!(chunk.references_outer_names);
chunk.emit_u16(Op::GetLocalSlot, 0, 1);
chunk.emit(Op::MulInt, 1);
assert!(chunk.references_outer_names);
}
#[test]
fn freeze_thaw_round_trips_references_outer_names() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetVar, 0, 1);
assert!(chunk.references_outer_names);
let frozen = chunk.freeze_for_cache();
let thawed = Chunk::from_cached(frozen);
assert!(thawed.references_outer_names);
let plain = Chunk::new();
assert!(!plain.references_outer_names);
let frozen_plain = plain.freeze_for_cache();
let thawed_plain = Chunk::from_cached(frozen_plain);
assert!(!thawed_plain.references_outer_names);
}
#[test]
fn cached_chunk_hydration_moves_owned_storage() {
let mut nested_chunk = Chunk::new();
nested_chunk.emit(Op::Return, 7);
let nested = CompiledFunction {
name: "nested".to_string(),
type_params: vec!["T".to_string()],
nominal_type_names: vec!["Widget".to_string()],
params: vec![ParamSlot {
name: "value".to_string(),
type_expr: Some(TypeExpr::List(Box::new(TypeExpr::Named(
"Widget".to_string(),
)))),
runtime_guard: None,
has_default: false,
}],
default_start: None,
chunk: Arc::new(nested_chunk),
is_generator: false,
is_stream: false,
has_rest_param: false,
has_runtime_type_checks: false,
};
let mut chunk = Chunk::new();
chunk.source_file = Some("owned.harn".to_string());
chunk.add_constant(Constant::String("payload".to_string()));
chunk.add_local_slot("local".to_string(), false, 0);
chunk.emit(Op::Return, 11);
chunk.functions.push(Arc::new(nested));
let cached = chunk.freeze_for_cache();
let code = cached.code.as_ptr();
let constants = cached.constants.as_ptr();
let lines = cached.lines.as_ptr();
let columns = cached.columns.as_ptr();
let source_file = cached.source_file.as_ref().unwrap().as_ptr();
let local_slots = cached.local_slots.as_ptr();
let function_name = cached.functions[0].name.as_ptr();
let type_params = cached.functions[0].type_params.as_ptr();
let nominal_type_names = cached.functions[0].nominal_type_names.as_ptr();
let param_name = cached.functions[0].params[0].name.as_ptr();
let param_type_name = named_list_element(&cached.functions[0].params[0].type_expr).as_ptr();
let nested_code = cached.functions[0].chunk.code.as_ptr();
let hydrated = Chunk::from_cached(cached);
assert_eq!(hydrated.code.as_ptr(), code);
assert_eq!(hydrated.constants.as_ptr(), constants);
assert_eq!(hydrated.lines.as_ptr(), lines);
assert_eq!(hydrated.columns.as_ptr(), columns);
assert_eq!(hydrated.source_file.as_ref().unwrap().as_ptr(), source_file);
assert_eq!(hydrated.local_slots.as_ptr(), local_slots);
assert_eq!(hydrated.functions[0].name.as_ptr(), function_name);
assert_eq!(hydrated.functions[0].type_params.as_ptr(), type_params);
assert_eq!(
hydrated.functions[0].nominal_type_names.as_ptr(),
nominal_type_names
);
assert_eq!(hydrated.functions[0].params[0].name.as_ptr(), param_name);
assert_eq!(
named_list_element(&hydrated.functions[0].params[0].type_expr).as_ptr(),
param_type_name
);
assert_eq!(hydrated.functions[0].chunk.code.as_ptr(), nested_code);
}
#[test]
fn inline_cache_slot_returns_none_for_non_cacheable_offsets() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetLocalSlot, 0, 1);
chunk.emit(Op::Pop, 1);
chunk.emit(Op::Return, 1);
assert!(chunk.inline_cache_slot(0).is_none());
assert!(chunk.inline_cache_slot(3).is_none());
assert!(chunk.inline_cache_slot(4).is_none());
}
#[test]
fn inline_cache_slot_registered_for_adaptive_binary_op() {
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
assert_eq!(chunk.inline_cache_slot(0), Some(0));
}
#[test]
fn inline_cache_slot_distinct_for_sequential_adaptive_binary_ops() {
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
chunk.emit(Op::Sub, 1);
chunk.emit(Op::Mul, 1);
let s0 = chunk.inline_cache_slot(0).expect("Add slot");
let s1 = chunk.inline_cache_slot(1).expect("Sub slot");
let s2 = chunk.inline_cache_slot(2).expect("Mul slot");
assert_ne!(s0, s1);
assert_ne!(s1, s2);
assert_ne!(s0, s2);
}
#[test]
fn inline_cache_slot_returns_none_for_out_of_bounds_offset() {
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
assert!(chunk.inline_cache_slot(usize::MAX).is_none());
assert!(chunk.inline_cache_slot(chunk.code.len()).is_none());
assert!(chunk.inline_cache_slot(chunk.code.len() + 16).is_none());
}
#[test]
fn inline_cache_slot_for_get_property_and_method_call() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetProperty, 0, 1); chunk.emit_method_call(0, 1, 1); chunk.emit_method_call_opt(0, 1, 1); chunk.emit_u16(Op::GetPropertyOpt, 0, 1); assert!(chunk.inline_cache_slot(0).is_some(), "GetProperty");
assert!(chunk.inline_cache_slot(3).is_some(), "MethodCall");
assert!(chunk.inline_cache_slot(7).is_some(), "MethodCallOpt");
assert!(chunk.inline_cache_slot(11).is_some(), "GetPropertyOpt");
}
#[test]
fn inline_cache_slot_for_call_and_call_builtin() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::Call, 1, 1); let call_builtin_offset = chunk.code.len();
chunk.emit_call_builtin(BuiltinId::from_name("any"), 0, 1, 1);
assert!(chunk.inline_cache_slot(0).is_some(), "Op::Call IC slot");
assert!(
chunk.inline_cache_slot(call_builtin_offset).is_some(),
"Op::CallBuiltin IC slot"
);
}
#[test]
fn inline_cache_slot_register_is_idempotent_for_same_offset() {
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
let slot_before = chunk.inline_cache_slot(0).expect("first registration");
chunk.register_inline_cache(0);
let slot_after = chunk.inline_cache_slot(0).expect("re-registration");
assert_eq!(slot_before, slot_after);
}
#[test]
fn inline_cache_index_round_trips_through_cached_chunk() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetLocalSlot, 0, 1);
chunk.emit_u16(Op::Constant, 0, 1);
chunk.emit(Op::Add, 1);
chunk.emit(Op::Sub, 1);
chunk.emit_method_call(0, 1, 1);
chunk.emit_u8(Op::Call, 1, 1);
let live_slots: Vec<(usize, Option<usize>)> = (0..chunk.code.len())
.map(|o| (o, chunk.inline_cache_slot(o)))
.collect();
let frozen = chunk.freeze_for_cache();
let thawed = Chunk::from_cached(frozen);
let thawed_slots: Vec<(usize, Option<usize>)> = (0..thawed.code.len())
.map(|o| (o, thawed.inline_cache_slot(o)))
.collect();
assert_eq!(live_slots, thawed_slots);
}
#[test]
fn inline_cache_index_agrees_with_btreemap_view() {
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
chunk.emit_u16(Op::GetVar, 0, 1);
chunk.emit(Op::LessInt, 1);
chunk.emit_u8(Op::Call, 2, 1);
chunk.emit(Op::Equal, 1);
chunk.emit_u16(Op::GetProperty, 0, 1);
chunk.emit_method_call_opt(0, 0, 1);
for offset in 0..chunk.code.len() {
let from_map = chunk.inline_cache_slots.get(&offset).copied();
let from_index = chunk.inline_cache_slot(offset);
assert_eq!(from_index, from_map, "parity broken at offset {offset}");
}
}
#[test]
fn peek_adaptive_binary_returns_none_for_empty_slot() {
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
let slot = chunk.inline_cache_slot(0).expect("Add registers a slot");
assert!(chunk.peek_adaptive_binary_cache(slot).is_none());
}
#[test]
fn peek_adaptive_binary_returns_op_and_state_after_warmup() {
use super::{AdaptiveBinaryOp, AdaptiveBinaryState, BinaryShape, InlineCacheEntry};
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
let slot = chunk.inline_cache_slot(0).expect("Add registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::AdaptiveBinary {
op: AdaptiveBinaryOp::Add,
state: AdaptiveBinaryState::Warmup {
shape: BinaryShape::Int,
hits: 2,
},
},
);
let (op, state) = chunk
.peek_adaptive_binary_cache(slot)
.expect("warmed slot peek");
assert_eq!(op, AdaptiveBinaryOp::Add);
assert!(matches!(
state,
AdaptiveBinaryState::Warmup {
shape: BinaryShape::Int,
hits: 2
}
));
}
#[test]
fn peek_adaptive_binary_returns_none_for_non_binary_variants() {
use super::{InlineCacheEntry, PropertyCacheTarget};
let mut chunk = Chunk::new();
chunk.emit(Op::Add, 1);
let slot = chunk.inline_cache_slot(0).expect("Add registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Property {
name_idx: 0,
target: PropertyCacheTarget::ListCount,
},
);
assert!(chunk.peek_adaptive_binary_cache(slot).is_none());
}
#[test]
fn peek_adaptive_binary_returns_none_for_out_of_bounds_slot() {
let chunk = Chunk::new();
assert!(chunk.peek_adaptive_binary_cache(0).is_none());
assert!(chunk.peek_adaptive_binary_cache(usize::MAX).is_none());
}
#[test]
fn peek_adaptive_binary_state_is_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<super::AdaptiveBinaryState>();
assert_copy::<super::AdaptiveBinaryOp>();
assert_copy::<super::BinaryShape>();
}
#[test]
fn peek_method_cache_returns_none_for_empty_slot() {
let mut chunk = Chunk::new();
chunk.emit_method_call(0, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("MethodCall registers a slot");
assert!(chunk.peek_method_cache(slot).is_none());
}
#[test]
fn peek_method_cache_returns_triple_after_warmup() {
let mut chunk = Chunk::new();
chunk.emit_method_call(7, 2, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("MethodCall registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Method {
name_idx: 7,
argc: 2,
target: MethodCacheTarget::ListContains,
},
);
let (name_idx, argc, target) = chunk.peek_method_cache(slot).expect("warmed slot peek");
assert_eq!(name_idx, 7);
assert_eq!(argc, 2);
assert_eq!(target, MethodCacheTarget::ListContains);
}
#[test]
fn peek_method_cache_returns_none_for_non_method_variants() {
let mut chunk = Chunk::new();
chunk.emit_method_call(0, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("MethodCall registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Property {
name_idx: 0,
target: PropertyCacheTarget::ListCount,
},
);
assert!(chunk.peek_method_cache(slot).is_none());
}
#[test]
fn peek_method_cache_returns_none_for_out_of_bounds_slot() {
let chunk = Chunk::new();
assert!(chunk.peek_method_cache(0).is_none());
assert!(chunk.peek_method_cache(usize::MAX).is_none());
}
#[test]
fn peek_method_cache_target_is_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<super::MethodCacheTarget>();
}
#[test]
fn peek_property_cache_returns_none_for_empty_slot() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetProperty, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("GetProperty registers a slot");
assert!(chunk.peek_property_cache(slot).is_none());
}
#[test]
fn peek_property_cache_returns_pair_after_warmup_for_dict_field() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetProperty, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("GetProperty registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Property {
name_idx: 11,
target: PropertyCacheTarget::DictField(Arc::from("count")),
},
);
let (name_idx, target) = chunk
.peek_property_cache(slot)
.expect("warmed property slot peek");
assert_eq!(name_idx, 11);
match target {
PropertyCacheTarget::DictField(field) => assert_eq!(field.as_ref(), "count"),
other => panic!("expected DictField, got {other:?}"),
}
}
#[test]
fn peek_property_cache_returns_pair_for_unit_target() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetProperty, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("GetProperty registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Property {
name_idx: 3,
target: PropertyCacheTarget::ListCount,
},
);
let (name_idx, target) = chunk
.peek_property_cache(slot)
.expect("warmed property slot peek");
assert_eq!(name_idx, 3);
assert_eq!(target, PropertyCacheTarget::ListCount);
}
#[test]
fn peek_property_cache_returns_none_for_non_property_variants() {
let mut chunk = Chunk::new();
chunk.emit_u16(Op::GetProperty, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("GetProperty registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Method {
name_idx: 0,
argc: 0,
target: MethodCacheTarget::ListCount,
},
);
assert!(chunk.peek_property_cache(slot).is_none());
}
#[test]
fn peek_property_cache_returns_none_for_out_of_bounds_slot() {
let chunk = Chunk::new();
assert!(chunk.peek_property_cache(0).is_none());
assert!(chunk.peek_property_cache(usize::MAX).is_none());
}
#[test]
fn add_constant_keeps_signed_zero_and_nan_distinct() {
let mut chunk = Chunk::new();
let pos = chunk.add_constant(Constant::Float(0.0));
let neg = chunk.add_constant(Constant::Float(-0.0));
assert_ne!(pos, neg, "+0.0 and -0.0 must get distinct constant slots");
assert_eq!(pos, chunk.add_constant(Constant::Float(0.0)));
assert_eq!(neg, chunk.add_constant(Constant::Float(-0.0)));
let a = chunk.add_constant(Constant::Float(1.5));
assert_eq!(a, chunk.add_constant(Constant::Float(1.5)));
let nan_a = chunk.add_constant(Constant::Float(f64::from_bits(0x7ff8_0000_0000_0001)));
let nan_b = chunk.add_constant(Constant::Float(f64::from_bits(0x7ff8_0000_0000_0002)));
assert_ne!(
nan_a, nan_b,
"distinct NaN payloads must get distinct constant slots"
);
assert_eq!(
nan_a,
chunk.add_constant(Constant::Float(f64::from_bits(0x7ff8_0000_0000_0001)))
);
let s = chunk.add_constant(Constant::Int(7));
assert_eq!(s, chunk.add_constant(Constant::Int(7)));
}
#[test]
fn add_constant_uses_first_slot_after_many_unique_constants() {
let mut chunk = Chunk::new();
let first = chunk.add_constant(Constant::String("shared".to_string()));
for index in 0..10_000 {
let slot = chunk.add_constant(Constant::String(format!("unique_{index}")));
assert_eq!(slot as usize, index + 1);
}
assert_eq!(
first,
chunk.add_constant(Constant::String("shared".to_string())),
"duplicate lookup must return the original slot after index growth"
);
}
#[test]
fn constant_index_round_trips_through_cached_chunk() {
let mut chunk = Chunk::new();
let shared = chunk.add_constant(Constant::String("shared".to_string()));
for index in 0..128 {
chunk.add_constant(Constant::Int(index));
}
let frozen = chunk.freeze_for_cache();
let constant_count = frozen.constants.len();
let mut thawed = Chunk::from_cached(frozen);
assert_eq!(
shared,
thawed.add_constant(Constant::String("shared".to_string())),
"cache thaw must rebuild the constant side index"
);
let next = thawed.add_constant(Constant::String("new".to_string()));
assert_eq!(next as usize, constant_count);
}
#[test]
fn peek_direct_call_state_returns_none_for_empty_slot() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::Call, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("Op::Call registers a slot");
assert!(chunk.peek_direct_call_state(slot).is_none());
}
#[test]
fn peek_direct_call_state_returns_warmup_state() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::Call, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("Op::Call registers a slot");
let target = synthetic_direct_call_target();
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::DirectCall {
state: DirectCallState::Warmup {
argc: 2,
target: target.clone(),
hits: 1,
},
},
);
let state = chunk
.peek_direct_call_state(slot)
.expect("warmed direct-call slot peek");
match state {
DirectCallState::Warmup {
argc,
target: peeked_target,
hits,
} => {
assert_eq!(argc, 2);
assert_eq!(hits, 1);
assert_eq!(peeked_target, target);
}
other => panic!("expected Warmup, got {other:?}"),
}
}
#[test]
fn peek_direct_call_state_returns_specialized_state() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::Call, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("Op::Call registers a slot");
let target = synthetic_direct_call_target();
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::DirectCall {
state: DirectCallState::Specialized {
argc: 3,
target: target.clone(),
hits: 100,
misses: 0,
},
},
);
let state = chunk
.peek_direct_call_state(slot)
.expect("warmed direct-call slot peek");
match state {
DirectCallState::Specialized {
argc,
target: peeked_target,
hits,
misses,
} => {
assert_eq!(argc, 3);
assert_eq!(hits, 100);
assert_eq!(misses, 0);
assert_eq!(peeked_target, target);
}
other => panic!("expected Specialized, got {other:?}"),
}
}
#[test]
fn peek_direct_call_state_returns_none_for_non_direct_call_variants() {
let mut chunk = Chunk::new();
chunk.emit_u8(Op::Call, 0, 1);
let slot = chunk
.inline_cache_slot(0)
.expect("Op::Call registers a slot");
chunk.set_inline_cache_entry(
slot,
InlineCacheEntry::Property {
name_idx: 0,
target: PropertyCacheTarget::ListCount,
},
);
assert!(chunk.peek_direct_call_state(slot).is_none());
}
#[test]
fn peek_direct_call_state_returns_none_for_out_of_bounds_slot() {
let chunk = Chunk::new();
assert!(chunk.peek_direct_call_state(0).is_none());
assert!(chunk.peek_direct_call_state(usize::MAX).is_none());
}
fn synthetic_direct_call_target() -> DirectCallTarget {
use crate::value::VmClosure;
use crate::{CompiledFunction, VmEnv};
let func = CompiledFunction {
name: "synthetic".to_string(),
type_params: Vec::new(),
nominal_type_names: Vec::new(),
params: Vec::new(),
default_start: None,
chunk: Arc::new(Chunk::new()),
is_generator: false,
is_stream: false,
has_rest_param: false,
has_runtime_type_checks: false,
};
DirectCallTarget::Closure(Arc::new(VmClosure {
func: Arc::new(func),
env: VmEnv::new(),
source_dir: None,
module_functions: None,
module_state: None,
retained_module_scope: None,
}))
}