1use std::{
2 fs::{self, OpenOptions},
3 io::{self, Write},
4 path::{Path, PathBuf},
5 process::{Command, Stdio},
6};
7
8use flux_common::{
9 bug,
10 dbg::{self, SpanTrace},
11 result::ResultExt,
12};
13use flux_config as config;
14use flux_middle::{
15 def_id::{FluxDefId, MaybeExternId},
16 global_env::GlobalEnv,
17 queries::QueryErr,
18 rty::{BinOp, BvSize, PrettyMap, Sort, local_deps},
19};
20use itertools::Itertools;
21use rustc_data_structures::{fx::FxIndexSet, unord::UnordMap};
22use rustc_hir::def_id::DefId;
23use rustc_span::ErrorGuaranteed;
24
25use crate::{
26 fixpoint_encoding::{ConstDeps, InterpretedConst, SortDeps, fixpoint},
27 lean_format::{self, LeanCtxt, WithLeanCtxt, def_id_to_pascal_case, snake_case_to_pascal_case},
28};
29
30fn sort_name_fragment(sort: &Sort) -> String {
31 match sort {
32 Sort::Int => "Int".to_string(),
33 Sort::Bool => "Bool".to_string(),
34 Sort::Real => "Real".to_string(),
35 Sort::BitVec(BvSize::Fixed(n)) => format!("Bv{n}"),
36 Sort::BitVec(BvSize::Param(p)) => format!("BvParam{}", p.as_u32()),
37 Sort::BitVec(BvSize::Infer(_)) => "BvInfer".to_string(),
38 _ => {
39 format!("{sort:?}")
40 .chars()
41 .filter(|c| c.is_alphanumeric())
42 .collect()
43 }
44 }
45}
46
47fn prim_op_lean_name(op: &BinOp) -> String {
48 match op {
49 BinOp::Add(s) => format!("PrimOpAdd{}", sort_name_fragment(s)),
50 BinOp::Sub(s) => format!("PrimOpSub{}", sort_name_fragment(s)),
51 BinOp::Mul(s) => format!("PrimOpMul{}", sort_name_fragment(s)),
52 BinOp::Div(s) => format!("PrimOpDiv{}", sort_name_fragment(s)),
53 BinOp::Mod(s) => format!("PrimOpMod{}", sort_name_fragment(s)),
54 BinOp::BitAnd(s) => format!("PrimOpBitAnd{}", sort_name_fragment(s)),
55 BinOp::BitOr(s) => format!("PrimOpBitOr{}", sort_name_fragment(s)),
56 BinOp::BitXor(s) => format!("PrimOpBitXor{}", sort_name_fragment(s)),
57 BinOp::BitShl(s) => format!("PrimOpBitShl{}", sort_name_fragment(s)),
58 BinOp::BitShr(s) => format!("PrimOpBitShr{}", sort_name_fragment(s)),
59 BinOp::Gt(s) => format!("PrimOpGt{}", sort_name_fragment(s)),
60 BinOp::Ge(s) => format!("PrimOpGe{}", sort_name_fragment(s)),
61 BinOp::Lt(s) => format!("PrimOpLt{}", sort_name_fragment(s)),
62 BinOp::Le(s) => format!("PrimOpLe{}", sort_name_fragment(s)),
63 BinOp::Eq => "PrimOpEq".to_string(),
64 BinOp::Ne => "PrimOpNe".to_string(),
65 BinOp::And => "PrimOpAnd".to_string(),
66 BinOp::Or => "PrimOpOr".to_string(),
67 BinOp::Iff => "PrimOpIff".to_string(),
68 BinOp::Imp => "PrimOpImp".to_string(),
69 }
70}
71
72macro_rules! string_vec {
74 ($($s:expr),* $(,)?) => {
75 vec![$($s.to_string()),*]
76 };
77}
78
79fn vc_name(genv: GlobalEnv, def_id: DefId) -> String {
80 def_id_to_pascal_case(&def_id, &genv.tcx())
81}
82
83fn proof_name(genv: GlobalEnv, def_id: DefId) -> String {
84 format!("{}_proof", vc_name(genv, def_id))
85}
86
87fn project() -> String {
88 config::lean_project().to_string()
89}
90
91fn namespaced<F>(f: &mut fs::File, w: F) -> io::Result<()>
92where
93 F: Fn(&mut fs::File) -> io::Result<()>,
94{
95 let namespace = "F";
96 writeln!(f, "\nnamespace {namespace}\n")?;
97 w(f)?;
98 writeln!(f, "\nend {namespace}")
99}
100
101fn rename_dir_contents(src: &Path, dst: &Path) -> io::Result<()> {
104 if !dst.exists() {
106 fs::create_dir_all(dst)?;
107 }
108
109 for entry in fs::read_dir(src)? {
110 let entry = entry?;
111 let file_type = entry.file_type()?;
112 let src_path = entry.path();
113 let dst_path = dst.join(entry.file_name());
114
115 if file_type.is_dir() {
116 rename_dir_contents(&src_path, &dst_path)?;
118 } else {
119 fs::rename(&src_path, &dst_path)?;
123 }
124 }
125 Ok(())
126}
127
128fn constant_deps(expr: &fixpoint::Expr, acc: &mut FxIndexSet<fixpoint::Var>) {
129 match expr {
130 fixpoint::Expr::Var(v) if matches!(v, fixpoint::Var::Const(..)) => {
131 acc.insert(*v);
132 }
133 fixpoint::Expr::App(func, _, args, _) => {
134 constant_deps(func, acc);
135 args.iter().for_each(|expr| constant_deps(expr, acc));
136 }
137 fixpoint::Expr::And(inner) | fixpoint::Expr::Or(inner) => {
138 inner.iter().for_each(|expr| constant_deps(expr, acc));
139 }
140 fixpoint::Expr::Atom(_, inner)
141 | fixpoint::Expr::BinaryOp(_, inner)
142 | fixpoint::Expr::Imp(inner)
143 | fixpoint::Expr::Iff(inner)
144 | fixpoint::Expr::Let(_, inner) => {
145 inner.iter().for_each(|expr| constant_deps(expr, acc));
146 }
147 fixpoint::Expr::IfThenElse(inner) => {
148 inner.iter().for_each(|expr| constant_deps(expr, acc));
149 }
150 fixpoint::Expr::Quantifier(_, _, inner)
151 | fixpoint::Expr::Neg(inner)
152 | fixpoint::Expr::Not(inner)
153 | fixpoint::Expr::IsCtor(_, inner) => {
154 constant_deps(inner, acc);
155 }
156 fixpoint::Expr::Var(..) | fixpoint::Expr::Constant(..) | fixpoint::Expr::ThyFunc(..) => {}
157 }
158}
159
160pub fn finalize(genv: GlobalEnv) -> io::Result<()> {
161 let project = project();
162 let src = genv.temp_dir().path().join(&project);
163 let dst = final_project_path(genv);
164 if src.exists() { rename_dir_contents(&src, &dst) } else { Ok(()) }
165}
166
167fn final_project_path(genv: GlobalEnv) -> PathBuf {
168 genv.lean_parent_dir().join(project())
169}
170
171fn project_path(genv: GlobalEnv, kind: FileKind) -> PathBuf {
172 let project = project();
173 match kind {
174 FileKind::Flux => genv.temp_dir().path().join(project),
175 FileKind::User => final_project_path(genv),
176 }
177}
178
179fn run_proof(genv: GlobalEnv, def_id: DefId) -> io::Result<()> {
180 let proof_path = LeanFile::Proof(def_id).path(genv, true);
181 let out = Command::new("lake")
182 .arg("--quiet")
183 .arg("--log-level=error")
184 .arg("lean")
185 .arg(proof_path)
186 .arg("--")
187 .arg("--json")
188 .stdout(Stdio::piped())
189 .stderr(Stdio::piped())
190 .current_dir(project_path(genv, FileKind::User))
191 .spawn()?
192 .wait_with_output()?;
193 if !out.stderr.is_empty() {
194 let stderr =
195 std::str::from_utf8(&out.stderr).unwrap_or("Lean exited with a non-zero return code");
196 return Err(io::Error::other(stderr));
197 }
198 let stdout = std::str::from_utf8(&out.stdout).unwrap_or("");
199 if stdout.lines().any(|line| line.contains("\"hasSorry\"")) {
200 return Err(io::Error::other("proof uses `sorry`"));
201 }
202 Ok(())
203}
204
205fn run_check(genv: GlobalEnv, def_id: DefId) -> io::Result<()> {
206 let checking_path = LeanFile::Checking(def_id).path(genv, true);
207 let status = Command::new("lake")
208 .arg("--quiet")
209 .arg("--log-level=error")
210 .arg("lean")
211 .arg(checking_path)
212 .stdout(Stdio::null())
213 .stderr(Stdio::null())
214 .current_dir(project_path(genv, FileKind::User))
215 .spawn()?
216 .wait()?;
217 if status.success() {
218 Ok(())
219 } else {
220 Err(io::Error::other("Lean exited with a non-zero exit code"))
221 }
222}
223
224fn run_lean(genv: GlobalEnv, def_id: DefId) -> io::Result<()> {
225 dbg::log_verbose!("FLUX running lean proof for {def_id:?}");
226 run_proof(genv, def_id)?;
227 run_check(genv, def_id)?;
228 Ok(())
229}
230
231pub fn check_proof(genv: GlobalEnv, def_id: DefId) -> Result<(), ErrorGuaranteed> {
232 run_lean(genv, def_id)
233 .map_err(|_| {
234 let name = genv.tcx().def_path(def_id).to_string_no_crate_verbose();
235 let msg = format!("failed to check external proof for `crate{name}`");
236 let span = genv.tcx().def_span(def_id);
237 QueryErr::Emitted(genv.sess().dcx().handle().struct_span_err(span, msg).emit())
238 })
239 .emit(&genv)?;
240 Ok(())
241}
242
243fn create_file_with_dirs<P: AsRef<Path>>(path: P) -> io::Result<Option<fs::File>> {
245 let path = path.as_ref();
246 if let Some(parent) = path.parent() {
247 fs::create_dir_all(parent)?;
248 }
249 match OpenOptions::new().write(true).create_new(true).open(path) {
250 Ok(file) => Ok(Some(file)),
251 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(None),
252 Err(e) => Err(e),
253 }
254}
255
256fn create_or_truncate_file_with_dirs<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
258 let path = path.as_ref();
259 if let Some(parent) = path.parent() {
260 fs::create_dir_all(parent)?;
261 }
262 OpenOptions::new()
263 .write(true)
264 .create(true)
265 .truncate(true)
266 .open(path)
267}
268
269#[derive(Eq, PartialEq, Hash, Debug, Clone)]
270pub enum FileKind {
271 Flux,
273 User,
275}
276
277#[derive(Eq, PartialEq, Hash, Debug, Clone)]
279pub enum LeanFile {
280 Basic,
282 Fluxlib,
284 OpaqueSort(String),
286 Struct(String),
288 OpaqueFun(String),
290 Fun(String),
292 OpaqueConst(String),
294 Vc(DefId),
296 Proof(DefId),
298 Checking(DefId),
300}
301
302impl LeanFile {
303 fn kind(&self) -> FileKind {
304 match self {
305 LeanFile::Basic
306 | LeanFile::Fluxlib
307 | LeanFile::Vc(_)
308 | LeanFile::Checking(_)
309 | LeanFile::Fun(_)
310 | LeanFile::OpaqueConst(_)
311 | LeanFile::Struct(_) => FileKind::Flux,
312 LeanFile::OpaqueSort(_) | LeanFile::OpaqueFun(_) | LeanFile::Proof(_) => FileKind::User,
313 }
314 }
315
316 fn segments(&self, genv: GlobalEnv) -> Vec<String> {
317 let project_name = snake_case_to_pascal_case(&project());
318 match self {
319 LeanFile::Basic => {
320 string_vec![project_name, "Basic"]
321 }
322 LeanFile::Fluxlib => {
323 string_vec![project_name, "Flux", "Prelude"]
324 }
325 LeanFile::OpaqueSort(name) => {
326 string_vec![project_name, "User", "Struct", name]
328 }
329 LeanFile::Struct(name) => {
330 string_vec![project_name, "Flux", "Struct", name]
332 }
333 LeanFile::OpaqueFun(name) => {
334 string_vec![project_name, "User", "Fun", name]
336 }
337 LeanFile::Fun(name) => {
338 string_vec![project_name, "Flux", "Fun", name]
340 }
341 LeanFile::OpaqueConst(name) => {
342 string_vec![project_name, "Flux", "Const", name]
343 }
344 LeanFile::Vc(def_id) => {
345 let name = vc_name(genv, *def_id);
346 string_vec![project_name, "Flux", "VC", name]
347 }
348 LeanFile::Proof(def_id) => {
349 let name = format!("{}Proof", vc_name(genv, *def_id));
350 string_vec![project_name, "User", "Proof", name]
351 }
352 LeanFile::Checking(def_id) => {
353 let name = vc_name(genv, *def_id);
354 string_vec![project_name, "Flux", "Checking", name]
355 }
356 }
357 }
358
359 fn path(&self, genv: GlobalEnv, force_final: bool) -> PathBuf {
361 let mut path =
362 if force_final { final_project_path(genv) } else { project_path(genv, self.kind()) };
363 for segment in self.segments(genv) {
364 path = path.join(segment);
365 }
366 path.set_extension("lean");
367 path
368 }
369
370 pub fn import(&self, genv: GlobalEnv) -> String {
371 format!("import {}", self.segments(genv).join("."))
372 }
373}
374
375pub struct LeanEncoder<'genv, 'tcx> {
376 genv: GlobalEnv<'genv, 'tcx>,
377 def_id: MaybeExternId,
378 pretty_var_map: PrettyMap<fixpoint::LocalVar>,
379 sort_deps: SortDeps,
380 fun_deps: Vec<fixpoint::FunDef>,
381 constants: ConstDeps,
382 kvar_decls: Vec<fixpoint::KVarDecl>,
383 constraint: fixpoint::Constraint,
384 sort_files: UnordMap<fixpoint::DataSort, LeanFile>,
385 fun_files: UnordMap<FluxDefId, LeanFile>,
386 const_files: UnordMap<fixpoint::Var, LeanFile>,
387 primop_var_map: UnordMap<fixpoint::GlobalVar, String>,
388}
389
390impl<'genv, 'tcx> LeanEncoder<'genv, 'tcx> {
391 fn lean_cx(&self) -> LeanCtxt<'_, 'genv, 'tcx> {
392 LeanCtxt {
393 genv: self.genv,
394 pretty_var_map: &self.pretty_var_map,
395 adt_map: &self.sort_deps.adt_map,
396 opaque_adt_map: &self.sort_deps.opaque_sorts,
397 primop_var_map: &self.primop_var_map,
398 hide_sort_vars: false,
399 }
400 }
401
402 fn datasort_name(&self, sort: &fixpoint::DataSort) -> String {
403 let name = format!("{}", WithLeanCtxt { item: sort, cx: &self.lean_cx() });
404 snake_case_to_pascal_case(&name)
405 }
406
407 fn lean_file_for_fun(&self, fun: &fixpoint::FunDef) -> LeanFile {
408 let name = self.var_name(&fun.name);
409 if fun.body.is_some() { LeanFile::Fun(name) } else { LeanFile::OpaqueFun(name) }
410 }
411
412 fn lean_file_for_interpreted_const(&self, const_: &InterpretedConst) -> LeanFile {
413 let name = self.var_name(&const_.0.name);
414 LeanFile::Fun(name)
415 }
416
417 fn var_name(&self, var: &fixpoint::Var) -> String {
418 let name = format!("{}", WithLeanCtxt { item: var, cx: &self.lean_cx() });
419 snake_case_to_pascal_case(&name)
420 }
421
422 fn post_import_preamble(&self) -> &str {
423 "open Classical\nset_option linter.unusedVariables false\n"
424 }
425
426 fn new(
427 genv: GlobalEnv<'genv, 'tcx>,
428 def_id: MaybeExternId,
429 pretty_var_map: PrettyMap<fixpoint::LocalVar>,
430 sort_deps: SortDeps,
431 fun_deps: Vec<fixpoint::FunDef>,
432 constants: ConstDeps,
433 kvar_decls: Vec<fixpoint::KVarDecl>,
434 constraint: fixpoint::Constraint,
435 ) -> io::Result<Self> {
436 let primop_var_map: UnordMap<fixpoint::GlobalVar, String> = constants
437 .opaque
438 .iter()
439 .filter_map(|(decl, op)| {
440 if let fixpoint::Var::Const(gvar, None) = decl.name {
441 Some((gvar, prim_op_lean_name(op)))
442 } else {
443 None
444 }
445 })
446 .collect();
447 let mut encoder = Self {
448 genv,
449 def_id,
450 pretty_var_map,
451 sort_deps,
452 fun_deps,
453 constants,
454 kvar_decls,
455 constraint,
456 fun_files: UnordMap::default(),
457 sort_files: UnordMap::default(),
458 const_files: UnordMap::default(),
459 primop_var_map,
460 };
461 encoder.fun_files = encoder.fun_files();
462 encoder.sort_files = encoder.sort_files();
463 encoder.const_files = encoder.const_files();
464 Ok(encoder)
465 }
466
467 fn run(&self) -> io::Result<()> {
468 self.generate_lake_project_if_not_present()?;
469 self.generate_lib_if_absent()?;
470 self.generate_vc_file()?;
471 self.generate_proof_if_absent()?;
472 self.generate_checking_file()?;
473 Ok(())
474 }
475
476 fn fun_files(&self) -> UnordMap<FluxDefId, LeanFile> {
477 let mut res = UnordMap::default();
478 for fun_def in &self.fun_deps {
479 let fixpoint::Var::Global(_, did) = fun_def.name else {
480 bug!("expected global var with id")
481 };
482 let name = self.var_name(&fun_def.name);
483 let file = LeanFile::Fun(name);
484 res.insert(did, file);
485 }
486 res
487 }
488
489 fn sort_files(&self) -> UnordMap<fixpoint::DataSort, LeanFile> {
490 let mut res = UnordMap::default();
491 for (_, sort) in &self.sort_deps.opaque_sorts {
492 let data_sort = sort.name.clone();
493 let name = self.datasort_name(&sort.name);
494 let file = LeanFile::OpaqueSort(name);
495 res.insert(data_sort, file);
496 }
497 for data_decl in &self.sort_deps.data_decls {
498 let data_sort = data_decl.name.clone();
499 let name = self.datasort_name(&data_decl.name);
500 let file = LeanFile::Struct(name);
501 res.insert(data_sort, file);
502 }
503 res
504 }
505
506 fn const_files(&self) -> UnordMap<fixpoint::Var, LeanFile> {
507 let mut res = UnordMap::default();
508 for (decl, _) in &self.constants.interpreted {
509 res.insert(decl.name, LeanFile::Fun(self.var_name(&decl.name)));
510 }
511 for (decl, op) in &self.constants.opaque {
512 res.insert(decl.name, LeanFile::OpaqueConst(prim_op_lean_name(op)));
513 }
514 res
515 }
516
517 fn generate_lake_project_if_not_present(&self) -> io::Result<()> {
518 let path = project_path(self.genv, FileKind::User).join("lakefile.toml");
519 if !path.exists() {
520 Command::new("lake")
521 .current_dir(self.genv.lean_parent_dir())
522 .arg("+v4.28.0")
523 .arg("new")
524 .arg(project())
525 .arg("lib")
526 .spawn()
527 .and_then(|mut child| child.wait())
528 .map(|_| ())?;
529 }
530 Ok(())
531 }
532
533 fn generate_opaque_sort_file_if_not_present(
534 &self,
535 sort: &fixpoint::SortDecl,
536 ) -> io::Result<()> {
537 let name = self.datasort_name(&sort.name);
538 let file = &LeanFile::OpaqueSort(name);
539
540 let path = file.path(self.genv, false);
541 if let Some(mut file) = create_file_with_dirs(path)? {
542 writeln!(file, "{}", &LeanFile::Fluxlib.import(self.genv))?;
543 writeln!(file, "{}", self.post_import_preamble())?;
544 namespaced(&mut file, |f| {
545 writeln!(f, "def {} := sorry", WithLeanCtxt { item: sort, cx: &self.lean_cx() })
546 })?;
547 file.sync_all()?;
548 }
549 Ok(())
550 }
551
552 fn data_decl_dependencies(&self, data_decl: &fixpoint::DataDecl) -> Vec<&LeanFile> {
553 let name = &data_decl.name;
554 let mut acc = vec![];
555 data_decl.deps(&mut acc);
556 acc.into_iter()
557 .map(|data_sort| {
558 self.sort_files.get(&data_sort).unwrap_or_else(|| {
559 panic!(
560 "Missing sort file for dependency {:?} of data decl {:?}",
561 data_sort, name
562 )
563 })
564 })
565 .unique()
566 .collect()
567 }
568
569 fn generate_struct_file_if_not_present(
570 &self,
571 data_decl: &fixpoint::DataDecl,
572 ) -> io::Result<()> {
573 let name = self.datasort_name(&data_decl.name);
574 let file = &LeanFile::Struct(name);
575 let path = file.path(self.genv, false);
576 if let Some(mut file) = create_file_with_dirs(path)? {
578 writeln!(file, "{}", &LeanFile::Fluxlib.import(self.genv))?;
580 for dep in self.data_decl_dependencies(data_decl) {
582 writeln!(file, "{}", dep.import(self.genv))?;
583 }
584 writeln!(file, "{}", self.post_import_preamble())?;
585
586 namespaced(&mut file, |f| {
588 writeln!(f, "{}", WithLeanCtxt { item: data_decl, cx: &self.lean_cx() })
589 })?;
590 file.sync_all()?;
591 }
592 Ok(())
593 }
594
595 fn sort_file(&self, sort: &fixpoint::DataSort) -> &LeanFile {
596 self.sort_files
597 .get(sort)
598 .unwrap_or_else(|| panic!("Missing sort file for sort {:?}", sort))
599 }
600
601 fn fun_file(&self, did: &FluxDefId) -> &LeanFile {
602 self.fun_files
603 .get(did)
604 .unwrap_or_else(|| panic!("Missing fun file for fun {:?}", did))
605 }
606
607 fn const_file(&self, name: &fixpoint::Var) -> &LeanFile {
608 self.const_files
609 .get(name)
610 .unwrap_or_else(|| panic!("Missing const file for const {name:?}"))
611 }
612
613 fn fun_def_dependencies(&self, did: FluxDefId, fun_def: &fixpoint::FunDef) -> Vec<&LeanFile> {
614 let mut res = vec![];
615
616 let mut sorts = vec![];
618 fun_def.sort.deps(&mut sorts);
619 for data_sort in sorts {
620 res.push(self.sort_file(&data_sort));
621 }
622
623 if !self.genv.normalized_info(did).uif {
625 let body = self.genv.inlined_body(did);
626 for dep_id in local_deps(&body) {
627 res.push(self.fun_file(&dep_id.to_def_id()));
628 }
629 }
630
631 let mut deps = FxIndexSet::default();
632 if let Some(body) = &fun_def.body {
633 constant_deps(&body.expr, &mut deps);
634 }
635 for (decl, _) in &self.constants.interpreted {
636 if deps.contains(&decl.name) {
637 res.push(self.const_file(&decl.name));
638 }
639 }
640 res
641 }
642
643 fn generate_fun_def_file_if_not_present(
644 &self,
645 did: FluxDefId,
646 fun_def: &fixpoint::FunDef,
647 ) -> io::Result<()> {
648 let path = self.lean_file_for_fun(fun_def).path(self.genv, false);
649 if let Some(mut file) = create_file_with_dirs(path)? {
650 writeln!(file, "{}", &LeanFile::Fluxlib.import(self.genv))?;
652 for dep in self.fun_def_dependencies(did, fun_def) {
654 writeln!(file, "{}", dep.import(self.genv))?;
655 }
656 writeln!(file, "{}", self.post_import_preamble())?;
657
658 namespaced(&mut file, |f| {
660 writeln!(f, "{}", WithLeanCtxt { item: fun_def, cx: &self.lean_cx() })
661 })?;
662 file.sync_all()?;
663 }
664 Ok(())
665 }
666
667 fn generate_interpreted_const_file_if_not_present(
668 &self,
669 interpreted_const: &InterpretedConst,
670 ) -> io::Result<()> {
671 let (const_decl, _) = interpreted_const;
672 let path = self
673 .lean_file_for_interpreted_const(interpreted_const)
674 .path(self.genv, false);
675 if let Some(mut file) = create_file_with_dirs(path)? {
676 writeln!(file, "{}", &LeanFile::Fluxlib.import(self.genv))?;
678
679 let mut sort_deps = vec![];
680 const_decl.sort.deps(&mut sort_deps);
681 for dep in sort_deps {
682 writeln!(file, "{}", self.sort_file(&dep).import(self.genv))?;
683 }
684
685 writeln!(file, "{}", self.post_import_preamble())?;
686
687 namespaced(&mut file, |f| {
688 if let Some(comment) = &const_decl.comment {
689 writeln!(f, "--{comment}")?;
690 }
691 writeln!(f, "{}", WithLeanCtxt { item: interpreted_const, cx: &self.lean_cx() })
692 })?;
693 file.sync_all()?;
694 }
695 Ok(())
696 }
697
698 fn generate_opaque_const_file(
699 &self,
700 const_decl: &fixpoint::ConstDecl,
701 op: &BinOp,
702 ) -> io::Result<()> {
703 let stable_name = prim_op_lean_name(op);
704 let file = LeanFile::OpaqueConst(stable_name.clone());
705 let path = file.path(self.genv, false);
706 let mut file = create_or_truncate_file_with_dirs(path)?;
707 writeln!(file, "{}", &LeanFile::Fluxlib.import(self.genv))?;
708
709 let mut sort_deps = vec![];
710 const_decl.sort.deps(&mut sort_deps);
711 for dep in sort_deps {
712 writeln!(file, "{}", self.sort_file(&dep).import(self.genv))?;
713 }
714
715 writeln!(file, "{}", self.post_import_preamble())?;
716
717 namespaced(&mut file, |f| {
718 if let Some(comment) = &const_decl.comment {
719 writeln!(f, "--{comment}")?;
720 }
721 writeln!(
722 f,
723 "axiom {stable_name} : {}",
724 WithLeanCtxt { item: &const_decl.sort, cx: &self.lean_cx() }
725 )
726 })?;
727 file.sync_all()?;
728 Ok(())
729 }
730
731 fn generate_lib_if_absent(&self) -> io::Result<()> {
732 let path = LeanFile::Fluxlib.path(self.genv, false);
733 if let Some(mut file) = create_file_with_dirs(path)? {
734 writeln!(file, "-- FLUX LIBRARY [DO NOT MODIFY] --")?;
735 writeln!(
737 file,
738 "abbrev BitVec_shiftLeft {{ n : Nat }} (x s : BitVec n) : BitVec n := BitVec.shiftLeft x (s.toNat)"
739 )?;
740 writeln!(
741 file,
742 "abbrev BitVec_ushiftRight {{ n : Nat }} (x s : BitVec n) : BitVec n := BitVec.ushiftRight x (s.toNat)"
743 )?;
744 writeln!(
745 file,
746 "abbrev BitVec_sshiftRight {{ n : Nat }} (x s : BitVec n) : BitVec n := BitVec.sshiftRight x (s.toNat)"
747 )?;
748 writeln!(
749 file,
750 "abbrev BitVec_uge {{ n : Nat }} (x y : BitVec n) := (BitVec.ult x y).not"
751 )?;
752 writeln!(
753 file,
754 "abbrev BitVec_sge {{ n : Nat }} (x y : BitVec n) := (BitVec.slt x y).not"
755 )?;
756 writeln!(
757 file,
758 "abbrev BitVec_ugt {{ n : Nat }} (x y : BitVec n) := (BitVec.ule x y).not"
759 )?;
760 writeln!(
761 file,
762 "abbrev BitVec_sgt {{ n : Nat }} (x y : BitVec n) := (BitVec.sle x y).not"
763 )?;
764 writeln!(
765 file,
766 "abbrev BitVec_zeroExtend {{n : Nat}} (extra : Nat) (x : BitVec n) : BitVec (n + extra) := BitVec.zeroExtend (n + extra) x"
767 )?;
768 writeln!(
769 file,
770 "abbrev BitVec_signExtend {{n : Nat}} (extra : Nat) (x : BitVec n) : BitVec (n + extra) := BitVec.signExtend (n + extra) x"
771 )?;
772 writeln!(
773 file,
774 "abbrev SmtMap (t0 t1 : Type) [Inhabited t0] [BEq t0] [Inhabited t1] : Type := t0 -> t1"
775 )?;
776 writeln!(
777 file,
778 "abbrev SmtMap_default {{ t0 t1: Type }} (v : t1) [Inhabited t0] [BEq t0] [Inhabited t1] : SmtMap t0 t1 := fun _ => v"
779 )?;
780 writeln!(
781 file,
782 "abbrev SmtMap_store {{ t0 t1 : Type }} [Inhabited t0] [BEq t0] [Inhabited t1] (m : SmtMap t0 t1) (k : t0) (v : t1) : SmtMap t0 t1 :=\n fun x => if x == k then v else m x"
783 )?;
784 writeln!(
785 file,
786 "abbrev SmtMap_select {{ t0 t1 : Type }} [Inhabited t0] [BEq t0] [Inhabited t1] (m : SmtMap t0 t1) (k : t0) := m k"
787 )?;
788 }
789 Ok(())
790 }
791
792 fn generate_vc_prelude(&self) -> io::Result<()> {
793 self.generate_lib_if_absent()?;
795
796 for (_, sort) in &self.sort_deps.opaque_sorts {
798 self.generate_opaque_sort_file_if_not_present(sort)?;
799 }
800 for data_decl in &self.sort_deps.data_decls {
802 self.generate_struct_file_if_not_present(data_decl)?;
803 }
804 for fun_def in &self.fun_deps {
806 let fixpoint::Var::Global(_, did) = fun_def.name else {
807 bug!("expected global var with id")
808 };
809 self.generate_fun_def_file_if_not_present(did, fun_def)?;
810 }
811 for const_decl in &self.constants.interpreted {
813 self.generate_interpreted_const_file_if_not_present(const_decl)?;
814 }
815 for (const_decl, op) in &self.constants.opaque {
817 self.generate_opaque_const_file(const_decl, op)?;
818 }
819 Ok(())
820 }
821
822 fn generate_vc_imports(&self, file: &mut fs::File) -> io::Result<()> {
823 writeln!(file, "{}", LeanFile::Fluxlib.import(self.genv))?;
824
825 for (_, sort) in &self.sort_deps.opaque_sorts {
826 let name = self.datasort_name(&sort.name);
827 writeln!(file, "{}", LeanFile::OpaqueSort(name).import(self.genv))?;
828 }
829
830 for data_decl in &self.sort_deps.data_decls {
831 let name = self.datasort_name(&data_decl.name);
832 writeln!(file, "{}", LeanFile::Struct(name).import(self.genv))?;
833 }
834
835 for fun_def in &self.fun_deps {
836 writeln!(file, "{}", self.lean_file_for_fun(fun_def).import(self.genv))?;
837 }
838
839 for const_decl in &self.constants.interpreted {
840 writeln!(
841 file,
842 "{}",
843 self.lean_file_for_interpreted_const(const_decl)
844 .import(self.genv)
845 )?;
846 }
847
848 for (_, op) in &self.constants.opaque {
849 writeln!(file, "{}", LeanFile::OpaqueConst(prim_op_lean_name(op)).import(self.genv))?;
850 }
851
852 Ok(())
853 }
854
855 fn generate_vc_file(&self) -> io::Result<()> {
856 self.generate_vc_prelude()?;
858
859 let def_id = self.def_id.resolved_id();
861 let path = LeanFile::Vc(def_id).path(self.genv, false);
862 if let Some(mut file) = create_file_with_dirs(path)? {
863 self.generate_vc_imports(&mut file)?;
864 writeln!(file, "{}", self.post_import_preamble())?;
865
866 let vc_name = vc_name(self.genv, def_id);
867 namespaced(&mut file, |f| {
869 write!(
870 f,
871 "{}",
872 WithLeanCtxt {
873 item: lean_format::LeanKConstraint {
874 theorem_name: &vc_name,
875 kvars: &self.kvar_decls,
876 constr: &self.constraint,
877 should_fail: self
878 .def_id
879 .as_local()
880 .map(|def_id| self.genv.should_fail(def_id))
881 .unwrap_or(false)
882 },
883 cx: &self.lean_cx()
884 }
885 )
886 })?;
887 file.sync_all()?;
888 }
889
890 Ok(())
891 }
892
893 fn generate_proof_if_absent(&self) -> io::Result<()> {
894 let def_id = self.def_id.resolved_id();
895 let vc_name = vc_name(self.genv, def_id);
896 let proof_name = proof_name(self.genv, def_id);
897 let path = LeanFile::Proof(def_id).path(self.genv, false);
898
899 if let Some(mut file) = create_file_with_dirs(path)? {
900 writeln!(file, "{}", LeanFile::Fluxlib.import(self.genv))?;
901 writeln!(file, "{}", LeanFile::Vc(def_id).import(self.genv))?;
902 writeln!(file, "{}", self.post_import_preamble())?;
903 namespaced(&mut file, |f| {
904 writeln!(f, "def {proof_name} : {vc_name} := by")?;
905 writeln!(f, " unfold {vc_name}")?;
906 writeln!(f, " sorry")
907 })?;
908 file.sync_all()?;
909 }
910 Ok(())
911 }
912
913 fn generate_checking_file(&self) -> io::Result<()> {
914 let def_id = self.def_id.resolved_id();
915 let vc_name = vc_name(self.genv, def_id);
916 let proof_name = proof_name(self.genv, def_id);
917 let path = LeanFile::Checking(def_id).path(self.genv, false);
918
919 let mut file = create_or_truncate_file_with_dirs(path)?;
920 writeln!(file, "{}", LeanFile::Vc(def_id).import(self.genv))?;
921 writeln!(file, "{}", LeanFile::Proof(def_id).import(self.genv))?;
922 writeln!(file)?;
923 writeln!(file, "#check (F.{proof_name} : F.{vc_name})")?;
924 file.sync_all()?;
925 Ok(())
926 }
927
928 pub fn encode(
929 genv: GlobalEnv<'genv, 'tcx>,
930 def_id: MaybeExternId,
931 pretty_var_map: PrettyMap<fixpoint::LocalVar>,
932 sort_deps: SortDeps,
933 fun_deps: Vec<fixpoint::FunDef>,
934 constants: ConstDeps,
935 kvar_decls: Vec<fixpoint::KVarDecl>,
936 constraint: fixpoint::Constraint,
937 ) -> io::Result<()> {
938 let encoder = Self::new(
939 genv,
940 def_id,
941 pretty_var_map,
942 sort_deps,
943 fun_deps,
944 constants,
945 kvar_decls,
946 constraint,
947 )?;
948 encoder.run()?;
949 Ok(())
950 }
951}
952
953fn hyperlink_proof(genv: GlobalEnv, def_id: MaybeExternId) {
954 let proof_name = proof_name(genv, def_id.resolved_id());
955 let path = LeanFile::Proof(def_id.resolved_id()).path(genv, false);
956 if let Some(span) = genv.proven_externally(def_id.local_id()) {
957 let dst_span = SpanTrace::from_path(&path, 3, 5, proof_name.len());
958 dbg::hyperlink_json!(genv.tcx(), span, dst_span);
959 }
960}
961
962fn record_proof(genv: GlobalEnv, def_id: MaybeExternId) -> io::Result<()> {
963 let path = LeanFile::Basic.path(genv, false);
964
965 let mut file = match create_file_with_dirs(&path)? {
966 Some(mut file) => {
967 writeln!(file, "-- Flux Basic Imports [DO NOT MODIFY] --")?;
969 file
970 }
971 None => fs::OpenOptions::new().append(true).open(path)?,
972 };
973 writeln!(file, "{}", LeanFile::Checking(def_id.resolved_id()).import(genv))
974}
975
976pub fn log_proof(genv: GlobalEnv, def_id: MaybeExternId) -> Result<(), ErrorGuaranteed> {
980 hyperlink_proof(genv, def_id);
981 record_proof(genv, def_id)
982 .map_err(|_| {
983 let name = genv
984 .tcx()
985 .def_path(def_id.resolved_id())
986 .to_string_no_crate_verbose();
987 let msg = format!("failed to record proof for `{name}`");
988 let span = genv.tcx().def_span(def_id);
989 QueryErr::Emitted(genv.sess().dcx().handle().struct_span_err(span, msg).emit())
990 })
991 .emit(&genv)?;
992 Ok(())
993}