Skip to content

Commit a80f449

Browse files
Haofeiclaude
andcommitted
test(testgen): generate generic functions (monomorphization differential)
Declare unbounded generic helpers `fn gN<T>(p: read T) -> Int` and call them at varied concrete scalar types (`gN<Int>(p: read 5)`, `gN<String>(p: read "x")`, ...). Each call site monomorphizes to a distinct Rust instantiation while the VM erases the type parameter — both must agree. Unbounded `T` keeps generation valid (no bound to satisfy, no `take`/move tracking); the coverage is in instantiating the same generic at multiple types. Smoke (>=95% accept, vm==jit) and full N-way incl. compiled (40 cases) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 50f47be commit a80f449

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

crates/rss-testgen/src/generator.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ struct Generator<'a> {
3636
var_counter: usize,
3737
structs: Vec<StructDef>,
3838
sums: Vec<SumDef>,
39+
/// Names of declared unbounded generic helper functions `fn gN<T>(p: read T)
40+
/// -> Int`. Called with varied concrete type args to exercise monomorphization.
41+
generic_fns: Vec<String>,
3942
}
4043

4144
impl<'a> Generator<'a> {
@@ -45,6 +48,7 @@ impl<'a> Generator<'a> {
4548
var_counter: 0,
4649
structs: Vec::new(),
4750
sums: Vec::new(),
51+
generic_fns: Vec::new(),
4852
}
4953
}
5054

@@ -107,6 +111,18 @@ impl<'a> Generator<'a> {
107111
self.sums.push(def);
108112
}
109113

114+
let generic_count = self.seed.choice(3); // 0..=2
115+
for index in 0..generic_count {
116+
let name = format!("g{index}");
117+
let body = self.seed.range_i64(0, 1000);
118+
// Unbounded `T`: the body can't operate on an opaque value, so it
119+
// returns a constant; the coverage is in monomorphizing the call sites.
120+
source.push_str(&format!(
121+
"fn {name}<T>(p: read T) -> Int {{\n return {body}\n}}\n\n"
122+
));
123+
self.generic_fns.push(name);
124+
}
125+
110126
let mut functions: Vec<FnSig> = Vec::new();
111127
let fn_count = self.seed.choice(4); // 0..=3 helpers
112128
for index in 0..fn_count {
@@ -624,6 +640,10 @@ impl<'a> Generator<'a> {
624640
if fuel == 0 {
625641
return self.gen_atom(ty, scope);
626642
}
643+
// Occasionally satisfy an `Int` target with a monomorphized generic call.
644+
if *ty == Ty::Int && !self.generic_fns.is_empty() && fuel > 0 && self.seed.choice(4) == 0 {
645+
return self.gen_generic_call(scope, fuel);
646+
}
627647
match self.seed.weighted(&[3, 2, 4]) {
628648
0 => self.gen_atom(ty, scope),
629649
1 => self
@@ -746,6 +766,16 @@ impl<'a> Generator<'a> {
746766
format!("{}({args})", sig.name)
747767
}
748768

769+
/// `gN<C>(p: read <arg>)` — a generic helper instantiated at a concrete scalar
770+
/// type `C`. Returns `Int`. Different call sites monomorphize to distinct Rust
771+
/// instantiations while the VM erases the type — both must agree.
772+
fn gen_generic_call(&mut self, scope: &Scope, fuel: u32) -> String {
773+
let name = self.generic_fns[self.seed.choice(self.generic_fns.len())].clone();
774+
let concrete = self.pick_scalar();
775+
let arg = self.gen_expr(&concrete, scope, fuel.saturating_sub(1));
776+
format!("{name}<{}>(p: read {arg})", concrete.render())
777+
}
778+
749779
/// A type-specific compound expression.
750780
fn gen_compound(&mut self, ty: &Ty, scope: &Scope, fuel: u32) -> String {
751781
match ty {

0 commit comments

Comments
 (0)