1use crate::value::LocalValueId;
19
20use super::{
21 Callee,
22 mnemonic::{Args, MnemonicKind},
23};
24use smallvec::SmallVec;
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
29pub struct Map {
30 pub body: Callee,
33 pub src: LocalValueId,
35 pub captures: Vec<LocalValueId>,
38}
39
40impl MnemonicKind for Map {
41 fn opcode(&self) -> &'static str {
42 "map"
43 }
44
45 fn args(&self) -> Args {
46 let mut args = SmallVec::with_capacity(1 + self.captures.len());
47 args.push(self.src);
48 args.extend(self.captures.iter().copied());
49 args
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::{
56 testing::TestContext,
57 value::{
58 FunctionBody, ValueId,
59 insn::{Callee, Mnemonic, mnemonic::MnemonicKind},
60 },
61 };
62
63 #[test]
66 fn map_renders_as_fmap() {
67 let mut tc = TestContext::new();
68 let body = FunctionBody::make(&mut tc.ctx, "foo".into()).unwrap().id;
69 let host = FunctionBody::make(&mut tc.ctx, "host".into()).unwrap().id;
70 let entry = tc.ctx.get_or_make_block(0x2000, host);
71 {
72 let mut f = FunctionBody::from_id_mut(&mut tc.ctx, host);
73 f.set_root(entry).unwrap();
74 f.add_block(entry);
75 }
76 let i8 = tc.ctx.shared.types.get_or_make_int(1);
77 let array_ty = tc.ctx.shared.types.get_or_make_array(i8, 8);
78 let (src, cap) = {
79 let mut b = tc.ctx.builder(entry);
80 (b.push_param(8).id(), b.push_param(4).id())
81 };
82 if let ValueId::BlockParam(pid) = src {
83 tc.ctx.block_param_mut(pid).type_id = array_ty;
84 }
85
86 let plain = {
87 let mut b = tc.ctx.builder(entry);
88 b.push_map(body, src, Vec::new()).id()
89 };
90 let ValueId::Instruction(plain_id) = plain else {
91 unreachable!()
92 };
93 let rendered = tc.ctx.get_insn(plain_id).as_statement().to_string();
94 assert!(
95 rendered.contains("foo <$>"),
96 "map renders as fmap, got: {rendered}"
97 );
98
99 let with_cap = {
100 let mut b = tc.ctx.builder(entry);
101 b.push_map(body, src, vec![cap]).id()
102 };
103 let ValueId::Instruction(cap_id) = with_cap else {
104 unreachable!()
105 };
106 let rendered = tc.ctx.get_insn(cap_id).as_statement().to_string();
107 assert!(
108 rendered.contains("(foo ") && rendered.contains(") <$>"),
109 "a capturing map renders as a partial application, got: {rendered}"
110 );
111 }
112
113 #[test]
114 fn map_builds_with_array_result_and_symbol_body() {
115 let mut tc = TestContext::new();
116
117 let body = FunctionBody::make(&mut tc.ctx, "body".into()).unwrap().id;
119
120 let host = FunctionBody::make(&mut tc.ctx, "host".into()).unwrap().id;
122 let entry = tc.ctx.get_or_make_block(0x1000, host);
123 {
124 let mut f = FunctionBody::from_id_mut(&mut tc.ctx, host);
125 f.set_root(entry).unwrap();
126 f.add_block(entry);
127 }
128 let i8 = tc.ctx.shared.types.get_or_make_int(1);
129 let array_ty = tc.ctx.shared.types.get_or_make_array(i8, 20);
130
131 let (src, cap) = {
132 let mut b = tc.ctx.builder(entry);
133 (b.push_param(20).id(), b.push_param(4).id())
134 };
135 if let ValueId::BlockParam(pid) = src {
138 tc.ctx.block_param_mut(pid).type_id = array_ty;
139 }
140 let map_val = {
141 let mut b = tc.ctx.builder(entry);
142 b.push_map(body, src, vec![cap]).id()
143 };
144 let ValueId::Instruction(map_id) = map_val else {
145 panic!("push_map should yield an instruction value");
146 };
147
148 let m = match tc.ctx.get_insn(map_id).mnemonic().clone() {
149 Mnemonic::Map(m) => m,
150 other => panic!("expected Map, got {other:?}"),
151 };
152
153 assert_eq!(m.body, Callee::Real(body));
155 assert_eq!(
156 m.args().to_vec(),
157 vec![src.strip_func(), cap.strip_func()],
158 "src then captures are the operands"
159 );
160 assert!(
161 !m.args().contains(&ValueId::Function(body).strip_func()),
162 "body is not an operand"
163 );
164
165 assert_eq!(tc.ctx.type_of(map_val), array_ty);
167
168 let new_src = {
170 let mut b = tc.ctx.builder(entry);
171 b.push_param(20).id()
172 };
173 let mut rewritten = Mnemonic::Map(m);
174 rewritten.replace_value(src.strip_func(), new_src.strip_func());
175 let Mnemonic::Map(r) = rewritten else {
176 unreachable!()
177 };
178 assert_eq!(r.src, new_src.strip_func());
179 assert_eq!(
180 r.body,
181 Callee::Real(body),
182 "body symbol is untouched by replace_value"
183 );
184 assert_eq!(r.captures, vec![cap.strip_func()]);
185 }
186}