#[test]
fn test_monomorphization_module_exists() {
use crate::compiler::monomorphization::cache::{MonomorphizationCache, build_mono_key};
use shape_value::v2::ConcreteType;
let mut cache = MonomorphizationCache::new();
assert!(cache.is_empty(), "fresh cache must be empty");
assert_eq!(cache.len(), 0);
let key = build_mono_key("map", &[ConcreteType::I64, ConcreteType::String]);
assert_eq!(key, "map::i64_string");
cache.insert(key.clone(), 7);
assert_eq!(cache.lookup(&key), Some(7));
assert_eq!(cache.len(), 1);
let compiler = crate::compiler::BytecodeCompiler::new();
let _: &MonomorphizationCache = &compiler.monomorphization_cache;
}
#[cfg(test)]
mod mono_key_tests {
use shape_value::v2::ConcreteType;
#[test]
fn mono_key_for_array_of_int() {
let arr_int = ConcreteType::Array(Box::new(ConcreteType::I64));
let key = arr_int.mono_key();
assert_eq!(key, "array_i64");
assert!(key.contains("i64"));
assert!(key.contains("array"));
}
#[test]
fn mono_key_for_hashmap_string_to_array_of_number() {
let inner = ConcreteType::Array(Box::new(ConcreteType::F64));
let map_ty = ConcreteType::HashMap(Box::new(ConcreteType::String), Box::new(inner));
let key = map_ty.mono_key();
assert_eq!(key, "hashmap_string_array_f64");
assert!(key.contains("string"));
assert!(key.contains("array_f64"));
}
#[test]
fn mono_key_disambiguates_int_vs_number() {
let int_key = ConcreteType::I64.mono_key();
let num_key = ConcreteType::F64.mono_key();
assert_ne!(int_key, num_key);
assert_eq!(int_key, "i64");
assert_eq!(num_key, "f64");
let arr_int = ConcreteType::Array(Box::new(ConcreteType::I64)).mono_key();
let arr_num = ConcreteType::Array(Box::new(ConcreteType::F64)).mono_key();
assert_ne!(arr_int, arr_num);
}
}
#[cfg(test)]
mod e2e_tests {
use crate::test_utils::{compile_with_prelude, eval_with_prelude};
#[test]
fn test_map_int_specialization() {
let source = r#"
let arr = [1, 2, 3]
let result = arr.map(|x| x + 1)
result.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
assert!(
cache_keys.iter().any(|k| k.contains("map") && k.contains("i64")),
"expected a map specialization keyed on i64 in cache, got: {:?}",
cache_keys
);
let result = eval_with_prelude(source);
assert_eq!(result.as_i64(), Some(9));
}
#[test]
fn test_map_number_specialization() {
let source = r#"
let arr = [1.5, 2.7]
let result = arr.map(|x| x * 2.0)
result
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
assert!(
cache_keys.iter().any(|k| k.contains("map") && k.contains("f64")),
"expected a map specialization keyed on f64 in cache, got: {:?}",
cache_keys
);
}
#[test]
fn test_filter_preserves_type() {
let source = r#"
let arr = [1, 2, 3, 4, 5]
let evens = arr.filter(|x| x % 2 == 0)
evens.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
assert!(
cache_keys.iter().any(|k| k.contains("filter") && k.contains("i64")),
"expected a filter<i64> specialization in cache, got: {:?}",
cache_keys
);
let result = eval_with_prelude(source);
assert_eq!(result.as_i64(), Some(6));
}
#[test]
fn test_reduce_to_scalar() {
let source = r#"
let arr = [1, 2, 3]
let sum = arr.reduce(|acc, x| acc + x, 0)
sum
"#;
let result = eval_with_prelude(source);
assert_eq!(result.as_i64(), Some(6));
}
#[test]
fn test_two_callsites_same_type_share_specialization() {
let source = r#"
let arr1 = [1, 2, 3]
let r1 = arr1.map(|x| x + 1)
let arr2 = [10, 20, 30]
let r2 = arr2.map(|x| x + 1)
r1.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let map_specializations: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("i64"))
.collect();
assert_eq!(
map_specializations.len(),
1,
"two map<i64> call sites with identical closure bodies should share one specialization, got: {:?}",
map_specializations
);
}
#[test]
fn test_two_callsites_different_types_different_specializations() {
let source = r#"
let arr_int = [1, 2, 3]
let r1 = arr_int.map(|x| x + 1)
let arr_num = [1.0, 2.0, 3.0]
let r2 = arr_num.map(|x| x + 1.0)
r1.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let map_specializations: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map"))
.collect();
assert!(
map_specializations.len() >= 2,
"two distinct map specializations expected, got: {:?}",
map_specializations
);
let unique: std::collections::HashSet<&&String> =
map_specializations.iter().collect();
assert_eq!(unique.len(), map_specializations.len());
}
#[test]
#[ignore]
fn test_nested_generic_call() {
let source = r#"
let nested = [[1, 2], [3, 4]]
let flat = nested.flatten()
flat.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
assert!(
cache_keys.iter().any(|k| k.contains("flatten")),
"expected a flatten specialization in cache, got: {:?}",
cache_keys
);
let result = eval_with_prelude(source);
assert_eq!(result.as_i64(), Some(10));
}
#[test]
fn test_user_defined_generic_function() {
let source = r#"
fn identity<T>(x: T) -> T { x }
let a = identity(42)
let b = identity("hi")
a
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
let identity_specs: Vec<&String> = cache_keys
.iter()
.filter(|k| k.contains("identity"))
.collect();
assert!(
identity_specs.len() >= 2,
"expected two identity specializations (i64 and string), got: {:?}",
identity_specs
);
assert!(
identity_specs.iter().any(|k| k.contains("i64")),
"missing identity::i64, got: {:?}",
identity_specs
);
assert!(
identity_specs.iter().any(|k| k.contains("string")),
"missing identity::string, got: {:?}",
identity_specs
);
}
#[test]
fn test_no_monomorphization_for_concrete_function() {
let source = r#"
fn add(a: int, b: int) -> int { a + b }
add(1, 2)
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
assert!(
!cache_keys.iter().any(|k| k.contains("add")),
"concrete function `add` should NOT be in the monomorphization cache, got: {:?}",
cache_keys
);
}
#[test]
fn phase_c_map_closure_emits_specialized_body() {
let source = r#"
let arr = [1, 2, 3]
let result = arr.map(|x| x + 1)
result.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let has_phase_c_key = bytecode
.monomorphization_keys
.iter()
.any(|k| k.contains("map") && k.contains("closure_"));
assert!(
has_phase_c_key,
"expected a closure-aware map specialization, got: {:?}",
bytecode.monomorphization_keys
);
}
#[test]
fn phase_c_specialized_body_has_fewer_call_value_opcodes() {
use crate::bytecode::OpCode;
let source = r#"
let arr = [1, 2, 3]
arr.map(|x| x + 1)
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c = bytecode
.functions
.iter()
.find(|f| f.name.contains("map") && f.name.contains("closure_"))
.expect("expected Phase C specialization");
let type_only = bytecode
.functions
.iter()
.find(|f| {
f.name.contains("map")
&& f.name.contains("i64")
&& !f.name.contains("closure_")
});
fn count_call_value(
bc: &crate::bytecode::BytecodeProgram,
f: &crate::bytecode::Function,
) -> usize {
let start = f.entry_point as usize;
let end = start + f.body_length as usize;
bc.instructions[start..end.min(bc.instructions.len())]
.iter()
.filter(|i| i.opcode == OpCode::CallValue)
.count()
}
let phase_c_count = count_call_value(&bytecode, phase_c);
if let Some(type_only_fn) = type_only {
let type_only_count = count_call_value(&bytecode, type_only_fn);
assert!(
phase_c_count < type_only_count,
"Phase C '{}' has {} CallValue; type-only '{}' has {} — inlining did not reduce indirect dispatch",
phase_c.name,
phase_c_count,
type_only_fn.name,
type_only_count,
);
} else {
assert!(
phase_c_count <= 1,
"Phase C '{}' has {} CallValue opcodes — inlining regressed",
phase_c.name, phase_c_count
);
}
}
#[test]
fn phase_c_identical_closures_share_specialization() {
let source = r#"
let a = [1, 2, 3]
let r1 = a.map(|x| x * 2)
let b = [4, 5, 6]
let r2 = b.map(|x| x * 2)
r1.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("closure_"))
.collect();
assert_eq!(
phase_c_keys.len(),
1,
"two structurally identical closures should share ONE Phase C specialization, got: {:?}",
phase_c_keys
);
}
#[test]
fn phase_c_two_identical_closures_share_closure_type_id() {
let source = r#"
let a = [1, 2, 3]
let r = a.map(|x| x + 1)
let b = [4, 5, 6]
let s = b.map(|x| x + 1)
r.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("closure_"))
.collect();
assert_eq!(
phase_c_keys.len(),
1,
"expected one shared key for identical closures, got: {:?}",
phase_c_keys
);
}
#[test]
fn phase_c_non_closure_arg_skips_closure_specialization() {
let source = r#"
fn double(x: int) -> int { x * 2 }
let arr = [1, 2, 3]
arr.map(double)
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("closure_"))
.collect();
assert!(
phase_c_keys.is_empty(),
"passing a bare function name must not trigger Phase C specialization, got: {:?}",
phase_c_keys
);
}
#[test]
fn phase_c_filter_closure_key_has_bool_return() {
let source = r#"
let arr = [1, -2, 3, -4, 5]
arr.filter(|x| x > 0)
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("filter") && k.contains("closure_"))
.collect();
assert!(
phase_c_keys
.iter()
.any(|k| k.contains("_bool_b") || k.ends_with("_bool")),
"expected a filter closure specialization with bool return, got: {:?}",
phase_c_keys
);
}
#[test]
fn phase_c_captured_vs_uncaptured_closures_keyed_distinctly() {
let source = r#"
let a = [1, 2, 3]
let r1 = a.map(|x| x + 1)
let n = 10
let r2 = a.map(|x| x + n)
r1.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("closure_"))
.collect();
assert_eq!(
phase_c_keys.len(),
2,
"captured vs uncaptured closures must produce distinct Phase C keys, got: {:?}",
phase_c_keys
);
let mut unique: std::collections::HashSet<&&String> =
std::collections::HashSet::new();
for k in &phase_c_keys {
unique.insert(k);
}
assert_eq!(unique.len(), 2, "keys must be distinct: {:?}", phase_c_keys);
}
#[test]
fn phase_c_second_identical_call_hits_cache() {
let source = r#"
let a = [1, 2, 3]
let r1 = a.map(|x| x + 1)
let r2 = a.map(|x| x + 1)
r1.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("closure_"))
.collect();
assert_eq!(
phase_c_keys.len(),
1,
"second call with identical closure must hit the cache, got: {:?}",
phase_c_keys
);
}
#[test]
fn phase_c_reduce_single_closure_arg() {
let source = r#"
let arr = [1, 2, 3, 4, 5]
arr.reduce(|acc, x| acc + x, 0)
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("reduce") && k.contains("closure_"))
.collect();
assert!(
!phase_c_keys.is_empty(),
"reduce should trigger Phase C specialization, got: {:?}",
bytecode.monomorphization_keys
);
}
#[test]
fn phase_c_different_bodies_same_captures_distinct_specializations() {
let source = r#"
let a = [1, 2, 3]
let r1 = a.map(|x| x + 1)
let r2 = a.map(|x| x * 2)
r1.sum()
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let phase_c_keys: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("map") && k.contains("closure_"))
.collect();
assert_eq!(
phase_c_keys.len(),
2,
"structurally different closure bodies must produce distinct Phase C specializations, got: {:?}",
phase_c_keys
);
}
#[test]
fn phase_c_map_runtime_result_matches() {
let source = r#"
let arr = [1, 2, 3]
arr.map(|x| x + 10).sum()
"#;
let result = eval_with_prelude(source);
assert_eq!(result.as_i64(), Some(36));
}
#[test]
fn phase_c_filter_runtime_result_matches() {
let source = r#"
let arr = [1, -2, 3, -4, 5]
arr.filter(|x| x > 0).sum()
"#;
let result = eval_with_prelude(source);
assert_eq!(result.as_i64(), Some(9));
}
#[test]
fn test_impl_trait_method_monomorphization() {
let source = r#"
trait Searchable {
method has(value) -> bool;
}
impl Searchable for Vec {
method has(value) -> bool {
for item in self {
if item == value { return true }
}
false
}
}
let arr = [10, 20, 30]
arr.has(20)
"#;
let bytecode = compile_with_prelude(source).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
assert!(
cache_keys
.iter()
.any(|k| k.contains("has") && k.contains("i64")),
"expected a has specialization keyed on i64 in cache, got: {:?}",
cache_keys
);
let result = eval_with_prelude(source);
assert_eq!(
result.as_bool(),
Some(true),
"has(20) on [10,20,30] should return true"
);
}
}
#[cfg(test)]
mod phase_3a_trait_bounds {
use crate::bytecode::OpCode;
use crate::compiler::BytecodeCompiler;
use crate::test_utils::{eval, eval_result};
#[test]
fn clamp_with_ord_bound_specializes_for_int_and_number() {
let source = r#"
fn clamp<T: Ord>(x: T, lo: T, hi: T) -> T {
if x < lo { lo }
else if x > hi { hi }
else { x }
}
let a = clamp(15, 0, 10)
let b = clamp(0.5, 0.0, 1.0)
a
"#;
let program = shape_ast::parser::parse_program(source).expect("parse failed");
let compiler = BytecodeCompiler::new();
let bytecode = compiler.compile(&program).expect("compile failed");
let cache_keys = &bytecode.monomorphization_keys;
let clamp_specs: Vec<&String> = cache_keys
.iter()
.filter(|k| k.contains("clamp"))
.collect();
assert!(
clamp_specs.iter().any(|k| k.contains("i64")),
"missing clamp::i64 specialization, got: {:?}",
clamp_specs
);
assert!(
clamp_specs.iter().any(|k| k.contains("f64")),
"missing clamp::f64 specialization, got: {:?}",
clamp_specs
);
let val = eval(source);
assert_eq!(val.as_i64(), Some(10), "clamp(15, 0, 10) should be 10");
}
#[test]
fn specialized_body_uses_typed_int_compare_opcodes() {
let source = r#"
fn clamp<T: Ord>(x: T, lo: T, hi: T) -> T {
if x < lo { lo }
else if x > hi { hi }
else { x }
}
clamp(15, 0, 10)
"#;
let program = shape_ast::parser::parse_program(source).expect("parse failed");
let compiler = BytecodeCompiler::new();
let bytecode = compiler.compile(&program).expect("compile failed");
let i64_idx = bytecode
.monomorphization_keys
.iter()
.position(|k| k.contains("clamp") && k.contains("i64"));
assert!(i64_idx.is_some(), "clamp::i64 must be in the cache");
let spec_fn = bytecode
.functions
.iter()
.find(|f| f.name.contains("clamp") && f.name.contains("i64"))
.expect("clamp::i64 function not found");
let body = &bytecode.instructions[spec_fn.entry_point as usize
..(spec_fn.entry_point as usize + spec_fn.body_length as usize)];
let opcodes: Vec<OpCode> = body.iter().map(|ins| ins.opcode).collect();
assert!(
opcodes.iter().any(|op| matches!(op, OpCode::LtInt)),
"specialized clamp::i64 body should emit LtInt; got: {:?}",
opcodes
);
assert!(
opcodes.iter().any(|op| matches!(op, OpCode::GtInt)),
"specialized clamp::i64 body should emit GtInt; got: {:?}",
opcodes
);
}
#[test]
fn specialized_body_uses_typed_number_compare_opcodes() {
let source = r#"
fn clamp<T: Ord>(x: T, lo: T, hi: T) -> T {
if x < lo { lo }
else if x > hi { hi }
else { x }
}
clamp(0.5, 0.0, 1.0)
"#;
let program = shape_ast::parser::parse_program(source).expect("parse failed");
let compiler = BytecodeCompiler::new();
let bytecode = compiler.compile(&program).expect("compile failed");
let spec_fn = bytecode
.functions
.iter()
.find(|f| f.name.contains("clamp") && f.name.contains("f64"))
.expect("clamp::f64 function not found");
let body = &bytecode.instructions[spec_fn.entry_point as usize
..(spec_fn.entry_point as usize + spec_fn.body_length as usize)];
let opcodes: Vec<OpCode> = body.iter().map(|ins| ins.opcode).collect();
assert!(
opcodes.iter().any(|op| matches!(op, OpCode::LtNumber)),
"specialized clamp::f64 body should emit LtNumber; got: {:?}",
opcodes
);
assert!(
opcodes.iter().any(|op| matches!(op, OpCode::GtNumber)),
"specialized clamp::f64 body should emit GtNumber; got: {:?}",
opcodes
);
}
#[test]
fn calling_iterable_bounded_fn_with_int_fails() {
let source = r#"
fn require_iter<T: Iterable>(x: T) -> T { x }
require_iter(42)
"#;
let result = eval_result(source);
assert!(
result.is_err(),
"expected compile error: int does not impl Iterable"
);
let msg = format!("{:?}", result.unwrap_err());
assert!(
msg.contains("trait bound not satisfied")
&& msg.contains("int")
&& msg.contains("Iterable"),
"expected bound-violation diagnostic, got: {msg}"
);
}
#[test]
fn two_int_callsites_share_one_specialization() {
let source = r#"
fn clamp<T: Ord>(x: T, lo: T, hi: T) -> T {
if x < lo { lo }
else if x > hi { hi }
else { x }
}
let a = clamp(5, 0, 10)
let b = clamp(20, 0, 10)
a + b
"#;
let program = shape_ast::parser::parse_program(source).expect("parse failed");
let compiler = BytecodeCompiler::new();
let bytecode = compiler.compile(&program).expect("compile failed");
let i64_specs: Vec<&String> = bytecode
.monomorphization_keys
.iter()
.filter(|k| k.contains("clamp") && k.contains("i64"))
.collect();
assert_eq!(
i64_specs.len(),
1,
"two int call sites must share one clamp::i64 specialization, got: {:?}",
i64_specs
);
let val = eval(source);
assert_eq!(val.as_i64(), Some(15));
}
}