1use solar_data_structures::newtype_index;
6
7mod types;
8pub use types::MirType;
9
10mod value;
11pub use value::{Immediate, Value};
12
13mod inst;
14pub use inst::{
15 EffectKind, InstKind, Instruction, InstructionMetadata, MemoryRegion, StorageAlias,
16};
17
18mod block;
19pub use block::{BasicBlock, Terminator};
20
21mod function;
22pub use function::{Function, FunctionAttributes};
23
24mod module;
25pub use module::{DataSegment, IMMUTABLE_WORD_SIZE, ImmutableSlot, Module, StorageSlot};
26
27mod builder;
28pub use builder::FunctionBuilder;
29
30mod display;
31
32mod parser;
33pub use parser::{ParseError, parse_function, parse_module};
34
35pub(crate) mod utils;
36
37newtype_index! {
38 pub struct ValueId;
40}
41
42newtype_index! {
43 pub struct InstId;
45}
46
47newtype_index! {
48 pub struct BlockId;
50}
51
52newtype_index! {
53 pub struct FunctionId;
55}
56
57#[cfg(test)]
72mod round_trip {
73 use super::{Module, parse_module};
74 use crate::{analysis::validate_module, lower};
75 use solar_interface::{ColorChoice, Session};
76 use solar_sema::Compiler;
77 use std::{
78 ops::ControlFlow,
79 path::{Path, PathBuf},
80 };
81
82 fn ui_codegen_dir() -> PathBuf {
84 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
85 .join("..")
86 .join("..")
87 .join("tests")
88 .join("ui")
89 .join("codegen")
90 }
91
92 fn first_diff<'a>(a: &'a str, b: &'a str) -> Option<(usize, &'a str, &'a str)> {
96 a.lines()
97 .zip(b.lines())
98 .enumerate()
99 .find(|(_, (la, lb))| la != lb)
100 .map(|(i, (la, lb))| (i + 1, la, lb))
101 }
102
103 #[test]
104 fn round_trip_all_sol_files() {
105 let dir = ui_codegen_dir();
106 assert!(dir.exists(), "ui codegen dir not found: {}", dir.display());
107
108 let mut failures: Vec<String> = Vec::new();
109 let mut count = 0usize;
110 for entry in std::fs::read_dir(&dir).unwrap() {
111 let path = entry.unwrap().path();
112 if path.extension().and_then(|s| s.to_str()) != Some("sol") {
113 continue;
114 }
115 count += 1;
116 if let Err(e) = round_trip_sol(&path) {
117 let name = path.file_name().unwrap().to_string_lossy().into_owned();
118 failures.push(format!("{name}: {e}"));
119 }
120 }
121 assert!(count > 0, "no .sol fixtures found in {}", dir.display());
122 assert!(
123 failures.is_empty(),
124 "{} round-trip failure(s):\n {}",
125 failures.len(),
126 failures.join("\n ")
127 );
128 }
129
130 #[test]
131 fn validate_all_lowered_sol_modules() {
132 let dir = ui_codegen_dir();
135 let mut failures: Vec<String> = Vec::new();
136 let mut count = 0usize;
137 for entry in std::fs::read_dir(&dir).unwrap() {
138 let path = entry.unwrap().path();
139 if path.extension().and_then(|s| s.to_str()) != Some("sol") {
140 continue;
141 }
142 count += 1;
143 if let Err(e) = validate_sol(&path) {
144 let name = path.file_name().unwrap().to_string_lossy().into_owned();
145 failures.push(format!("{name}: {e}"));
146 }
147 }
148 assert!(count > 0, "no .sol fixtures found");
149 assert!(
150 failures.is_empty(),
151 "{} validation failure(s):\n {}",
152 failures.len(),
153 failures.join("\n ")
154 );
155 }
156
157 fn validate_sol(path: &Path) -> Result<(), String> {
158 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
159 let mut compiler = Compiler::new(sess);
160
161 let parse_result = compiler.enter_mut(|c| -> solar_interface::Result<()> {
162 let mut pcx = c.parse();
163 pcx.load_files([path])?;
164 pcx.parse();
165 Ok(())
166 });
167 if parse_result.is_err() {
168 return Err("parse failed".into());
169 }
170
171 let mut result: Result<(), String> = Ok(());
172 let _ = compiler.enter_mut(|c| -> solar_interface::Result<()> {
173 let ControlFlow::Continue(()) = c.lower_asts()? else { return Ok(()) };
174 let ControlFlow::Continue(()) = c.analysis()? else { return Ok(()) };
175 let gcx = c.gcx();
176 for id in gcx.hir.contract_ids() {
177 let contract = gcx.hir.contract(id);
178 if contract.kind.is_interface() || contract.kind.is_abstract_contract() {
179 continue;
180 }
181 let module = lower::lower_contract(gcx, id);
182 let errors = validate_module(&module);
183 if !errors.is_empty() {
184 result = Err(format!(
185 "contract `{}` has {} validation error(s):\n {}",
186 contract.name,
187 errors.len(),
188 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join("\n ")
189 ));
190 return Ok(());
191 }
192 }
193 Ok(())
194 });
195 result
196 }
197
198 #[test]
199 fn round_trip_all_mir_files() {
200 let dir = ui_codegen_dir().join("mir");
201 assert!(dir.exists(), "mir test dir not found: {}", dir.display());
202
203 let mut failures: Vec<String> = Vec::new();
204 let mut count = 0usize;
205 for entry in std::fs::read_dir(&dir).unwrap() {
206 let path = entry.unwrap().path();
207 if path.extension().and_then(|s| s.to_str()) != Some("mir") {
208 continue;
209 }
210 count += 1;
211 if let Err(e) = round_trip_mir(&path) {
212 let name = path.file_name().unwrap().to_string_lossy().into_owned();
213 failures.push(format!("{name}: {e}"));
214 }
215 }
216 assert!(count > 0, "no .mir fixtures found in {}", dir.display());
217 assert!(
218 failures.is_empty(),
219 "{} round-trip failure(s):\n {}",
220 failures.len(),
221 failures.join("\n ")
222 );
223 }
224
225 fn round_trip_sol(path: &Path) -> Result<(), String> {
228 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
229 let mut compiler = Compiler::new(sess);
230
231 let parse_result = compiler.enter_mut(|c| -> solar_interface::Result<()> {
232 let mut pcx = c.parse();
233 pcx.load_files([path])?;
234 pcx.parse();
235 Ok(())
236 });
237 if parse_result.is_err() {
238 return Err("parse failed".into());
239 }
240
241 let mut result: Result<(), String> = Ok(());
242 let _ = compiler.enter_mut(|c| -> solar_interface::Result<()> {
243 let ControlFlow::Continue(()) = c.lower_asts()? else {
244 return Ok(());
245 };
246 let ControlFlow::Continue(()) = c.analysis()? else {
247 return Ok(());
248 };
249
250 let gcx = c.gcx();
251 for id in gcx.hir.contract_ids() {
252 let contract = gcx.hir.contract(id);
253 if contract.kind.is_interface() || contract.kind.is_abstract_contract() {
254 continue;
255 }
256 let module = lower::lower_contract(gcx, id);
257 if let Err(e) = check_round_trip_module(&module) {
258 result = Err(format!("contract `{}`: {e}", contract.name));
259 return Ok(());
260 }
261 }
262 Ok(())
263 });
264 result
265 }
266
267 fn round_trip_mir(path: &Path) -> Result<(), String> {
269 #[allow(clippy::disallowed_methods)]
271 let raw = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
272 let text: String =
276 raw.lines().filter(|l| !l.starts_with("//@")).collect::<Vec<_>>().join("\n");
277
278 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
279 let mut result: Result<(), String> = Ok(());
280 sess.enter(|| {
281 let parsed1 = match parse_module(&text) {
282 Ok(m) => m,
283 Err(e) => {
284 result = Err(format!("first parse failed: {e}"));
285 return;
286 }
287 };
288 let print1 = parsed1.to_text().to_string();
289 let parsed2 = match parse_module(&print1) {
290 Ok(m) => m,
291 Err(e) => {
292 result = Err(format!("second parse failed: {e}"));
293 return;
294 }
295 };
296 let print2 = parsed2.to_text().to_string();
297 if print1 != print2 {
298 let diff = first_diff(&print1, &print2)
299 .map(|(i, a, b)| format!("line {i}: `{a}` vs `{b}`"))
300 .unwrap_or_else(|| "(length mismatch)".to_string());
301 result = Err(format!("not idempotent: {diff}"));
302 }
303 });
304 result
305 }
306
307 fn check_round_trip_module(module: &Module) -> Result<(), String> {
310 let print1 = module.to_text().to_string();
311 let parsed1 = parse_module(&print1)
312 .map_err(|e| format!("first parse: {e}\n--- print1 ---\n{print1}"))?;
313 let print2 = parsed1.to_text().to_string();
314 let parsed2 = parse_module(&print2).map_err(|e| {
315 format!("second parse: {e}\n--- print1 ---\n{print1}\n--- print2 ---\n{print2}")
316 })?;
317 let print3 = parsed2.to_text().to_string();
318
319 if print2 != print3 {
320 let diff = first_diff(&print2, &print3)
321 .map(|(i, a, b)| format!("line {i}: `{a}` vs `{b}`"))
322 .unwrap_or_else(|| "(length mismatch)".to_string());
323 return Err(format!(
324 "not idempotent: {diff}\n--- print2 ---\n{print2}\n--- print3 ---\n{print3}"
325 ));
326 }
327 Ok(())
328 }
329}