use crate::value::LocalValueId;
use super::{
Callee,
mnemonic::{Args, MnemonicKind},
};
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Map {
pub body: Callee,
pub src: LocalValueId,
pub captures: Vec<LocalValueId>,
}
impl MnemonicKind for Map {
fn opcode(&self) -> &'static str {
"map"
}
fn args(&self) -> Args {
let mut args = SmallVec::with_capacity(1 + self.captures.len());
args.push(self.src);
args.extend(self.captures.iter().copied());
args
}
}
#[cfg(test)]
mod tests {
use crate::{
testing::TestContext,
value::{
FunctionBody, ValueId,
insn::{Callee, Mnemonic, mnemonic::MnemonicKind},
},
};
#[test]
fn map_renders_as_fmap() {
let mut tc = TestContext::new();
let body = FunctionBody::make(&mut tc.ctx, "foo".into()).unwrap().id;
let host = FunctionBody::make(&mut tc.ctx, "host".into()).unwrap().id;
let entry = tc.ctx.get_or_make_block(0x2000, host);
{
let mut f = FunctionBody::from_id_mut(&mut tc.ctx, host);
f.set_root(entry).unwrap();
f.add_block(entry);
}
let i8 = tc.ctx.shared.types.get_or_make_int(1);
let array_ty = tc.ctx.shared.types.get_or_make_array(i8, 8);
let (src, cap) = {
let mut b = tc.ctx.builder(entry);
(b.push_param(8).id(), b.push_param(4).id())
};
if let ValueId::BlockParam(pid) = src {
tc.ctx.block_param_mut(pid).type_id = array_ty;
}
let plain = {
let mut b = tc.ctx.builder(entry);
b.push_map(body, src, Vec::new()).id()
};
let ValueId::Instruction(plain_id) = plain else {
unreachable!()
};
let rendered = tc.ctx.get_insn(plain_id).as_statement().to_string();
assert!(
rendered.contains("foo <$>"),
"map renders as fmap, got: {rendered}"
);
let with_cap = {
let mut b = tc.ctx.builder(entry);
b.push_map(body, src, vec![cap]).id()
};
let ValueId::Instruction(cap_id) = with_cap else {
unreachable!()
};
let rendered = tc.ctx.get_insn(cap_id).as_statement().to_string();
assert!(
rendered.contains("(foo ") && rendered.contains(") <$>"),
"a capturing map renders as a partial application, got: {rendered}"
);
}
#[test]
fn map_builds_with_array_result_and_symbol_body() {
let mut tc = TestContext::new();
let body = FunctionBody::make(&mut tc.ctx, "body".into()).unwrap().id;
let host = FunctionBody::make(&mut tc.ctx, "host".into()).unwrap().id;
let entry = tc.ctx.get_or_make_block(0x1000, host);
{
let mut f = FunctionBody::from_id_mut(&mut tc.ctx, host);
f.set_root(entry).unwrap();
f.add_block(entry);
}
let i8 = tc.ctx.shared.types.get_or_make_int(1);
let array_ty = tc.ctx.shared.types.get_or_make_array(i8, 20);
let (src, cap) = {
let mut b = tc.ctx.builder(entry);
(b.push_param(20).id(), b.push_param(4).id())
};
if let ValueId::BlockParam(pid) = src {
tc.ctx.block_param_mut(pid).type_id = array_ty;
}
let map_val = {
let mut b = tc.ctx.builder(entry);
b.push_map(body, src, vec![cap]).id()
};
let ValueId::Instruction(map_id) = map_val else {
panic!("push_map should yield an instruction value");
};
let m = match tc.ctx.get_insn(map_id).mnemonic().clone() {
Mnemonic::Map(m) => m,
other => panic!("expected Map, got {other:?}"),
};
assert_eq!(m.body, Callee::Real(body));
assert_eq!(
m.args().to_vec(),
vec![src.strip_func(), cap.strip_func()],
"src then captures are the operands"
);
assert!(
!m.args().contains(&ValueId::Function(body).strip_func()),
"body is not an operand"
);
assert_eq!(tc.ctx.type_of(map_val), array_ty);
let new_src = {
let mut b = tc.ctx.builder(entry);
b.push_param(20).id()
};
let mut rewritten = Mnemonic::Map(m);
rewritten.replace_value(src.strip_func(), new_src.strip_func());
let Mnemonic::Map(r) = rewritten else {
unreachable!()
};
assert_eq!(r.src, new_src.strip_func());
assert_eq!(
r.body,
Callee::Real(body),
"body symbol is untouched by replace_value"
);
assert_eq!(r.captures, vec![cap.strip_func()]);
}
}