1use core::fmt;
2use std::{fmt::Write, iter, sync::LazyLock};
3
4use flux_common::{
5 bug,
6 dbg::{self, as_subscript},
7};
8use flux_middle::{
9 def_id::FluxDefId,
10 global_env::GlobalEnv,
11 rty::{PrettyMap, PrettyVar},
12};
13use itertools::Itertools;
14use liquid_fixpoint::{FixpointFmt, Identifier, Quantifier, ThyFunc};
15use rustc_data_structures::{fx::FxIndexSet, unord::UnordMap};
16use rustc_hir::def_id::DefId;
17
18use crate::fixpoint_encoding::{
19 ClosedSolution, InterpretedConst,
20 fixpoint::{
21 self, AdtId, BinOp, BinRel, Constant, Constraint, DataDecl, DataField, DataSort, Expr,
22 FunDef, FunSort, GlobalVar, KVarDecl, KVid, LocalVar, Pred, Sort, SortCtor, SortDecl, Var,
23 },
24};
25
26pub struct LeanCtxt<'a, 'genv, 'tcx> {
27 pub genv: GlobalEnv<'genv, 'tcx>,
28 pub pretty_var_map: &'a PrettyMap<LocalVar>,
29 pub adt_map: &'a FxIndexSet<DefId>,
30 pub opaque_adt_map: &'a [(FluxDefId, SortDecl)],
31 pub primop_var_map: &'a UnordMap<GlobalVar, String>,
33 pub hide_sort_vars: bool,
34}
35
36pub struct WithLeanCtxt<'a, 'b, 'genv, 'tcx, T> {
37 pub item: T,
38 pub cx: &'a LeanCtxt<'b, 'genv, 'tcx>,
39}
40
41impl<'a, 'b, 'genv, 'tcx, T: LeanFmt> fmt::Display for WithLeanCtxt<'a, 'b, 'genv, 'tcx, T> {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 self.item.lean_fmt(f, self.cx)
44 }
45}
46
47pub trait LeanFmt {
48 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result;
49}
50
51pub struct LeanKConstraint<'a> {
52 pub theorem_name: &'a str,
53 pub kvars: &'a [KVarDecl],
54 pub constr: &'a Constraint,
55 pub should_fail: bool,
56}
57
58struct LeanThyFunc<'a>(&'a ThyFunc);
59
60impl LeanFmt for SortDecl {
61 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
62 self.name.lean_fmt(f, cx)?;
63 write!(
64 f,
65 " {} : Type",
66 (0..(self.vars))
67 .map(|i| format!("(t{i} : Type) [Inhabited t{i}]"))
68 .format(" ")
69 )
70 }
71}
72
73impl LeanFmt for InterpretedConst {
74 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
75 write!(
76 f,
77 "abbrev {} : {} := {}",
78 WithLeanCtxt { item: &self.0.name, cx },
79 WithLeanCtxt { item: &self.0.sort, cx },
80 WithLeanCtxt { item: &self.1, cx }
81 )
82 }
83}
84
85impl LeanFmt for DataField {
87 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
88 write!(
89 f,
90 "({} : {})",
91 sanitize_name(&self.name.display().to_string()),
92 WithLeanCtxt { item: &self.sort, cx }
93 )
94 }
95}
96
97impl LeanFmt for DataSort {
98 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> std::fmt::Result {
99 match self {
100 DataSort::User(opaque_id) => {
101 let (def_id, _) = cx.opaque_adt_map.get(opaque_id.as_usize()).unwrap();
102 write!(f, "{}", snake_case_to_pascal_case(def_id.name().as_str()))
103 }
104 DataSort::Tuple(n) => write!(f, "Tupleₓ{}", dbg::as_subscript(n)),
105 DataSort::Adt(adt_id) => {
106 let def_id = cx.adt_map.get_index(adt_id.as_usize()).unwrap();
107 write!(f, "{}", def_id_to_pascal_case(def_id, &cx.genv.tcx()))
108 }
109 }
110 }
111}
112
113impl LeanFmt for DataDecl {
114 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
115 if self.ctors.len() == 1 {
116 writeln!(f, "@[ext]")?;
117 write!(f, "structure ")?;
118 self.name.lean_fmt(f, cx)?;
119 writeln!(
120 f,
121 " {} where",
122 (0..self.vars)
123 .map(|i| format!("(t{i} : Type) [Inhabited t{i}]"))
124 .format(" ")
125 )?;
126 let ctor = &self.ctors[0];
127 if let fixpoint::Var::DataCtor(adt_id, _) = &ctor.name {
128 writeln!(
129 f,
130 " mk{}{} ::",
131 WithLeanCtxt { item: &DataSort::Adt(*adt_id), cx },
132 as_subscript(0)
133 )?;
134 for (idx, field) in ctor.fields.iter().enumerate() {
135 writeln!(
136 f,
137 " {} : {} ",
138 WithLeanCtxt { item: LeanField(*adt_id, idx.try_into().unwrap()), cx },
139 WithLeanCtxt { item: &field.sort, cx }
140 )?;
141 }
142 } else {
143 bug!("unexpected ctor {ctor:?} in datadecl");
144 };
145 } else {
146 write!(f, "inductive ")?;
147 self.name.lean_fmt(f, cx)?;
148 writeln!(
149 f,
150 " {} where",
151 (0..self.vars)
152 .map(|i| format!("(t{i} : Type) [Inhabited t{i}]"))
153 .format(" ")
154 )?;
155 for data_ctor in &self.ctors {
156 let fixpoint::Var::DataCtor(adt_id, variant_id) = &data_ctor.name else {
157 bug!("unexpected ctor {data_ctor:?} in datadecl")
158 };
159 write!(f, "| ")?;
160 write!(
161 f,
162 " mk{}{} ",
163 WithLeanCtxt { item: &DataSort::Adt(*adt_id), cx },
164 as_subscript(variant_id.as_usize()),
165 )?;
166 for field in &data_ctor.fields {
168 write!(f, " ")?;
169 field.lean_fmt(f, cx)?;
170 }
171 writeln!(f)?;
172 }
173 }
174 Ok(())
175 }
176}
177
178impl<'a> fmt::Display for LeanThyFunc<'a> {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 match self.0 {
181 ThyFunc::IntToBv8 => write!(f, "BitVec.ofInt 8"),
182 ThyFunc::IntToBv32 => write!(f, "BitVec.ofInt 32"),
183 ThyFunc::IntToBv64 => write!(f, "BitVec.ofInt 64"),
184 ThyFunc::Bv8ToInt | ThyFunc::Bv32ToInt | ThyFunc::Bv64ToInt => {
185 write!(f, "BitVec.toNat")
186 }
187 ThyFunc::BvAdd => write!(f, "BitVec.add"),
188 ThyFunc::BvSub => write!(f, "BitVec.sub"),
189 ThyFunc::BvMul => write!(f, "BitVec.mul"),
190 ThyFunc::BvNeg => write!(f, "BitVec.neg"),
191 ThyFunc::BvSdiv => write!(f, "BitVec.sdiv"),
192 ThyFunc::BvSrem => write!(f, "BitVec.srem"),
193 ThyFunc::BvUdiv => write!(f, "BitVec.udiv"),
194 ThyFunc::BvAnd => write!(f, "BitVec.and"),
195 ThyFunc::BvOr => write!(f, "BitVec.or"),
196 ThyFunc::BvXor => write!(f, "BitVec.xor"),
197 ThyFunc::BvNot => write!(f, "BitVec.not"),
198 ThyFunc::BvSle => write!(f, "BitVec.sle"),
199 ThyFunc::BvSlt => write!(f, "BitVec.slt"),
200 ThyFunc::BvUle => write!(f, "BitVec.ule"),
201 ThyFunc::BvUlt => write!(f, "BitVec.ult"),
202 ThyFunc::BvAshr => write!(f, "BitVec_sshiftRight"),
203 ThyFunc::BvLshr => write!(f, "BitVec_ushiftRight"),
204 ThyFunc::BvShl => write!(f, "BitVec_shiftLeft"),
205 ThyFunc::BvSignExtend(size) => write!(f, "BitVec_signExtend {}", size),
206 ThyFunc::BvZeroExtend(size) => write!(f, "BitVec_zeroExtend {}", size),
207 ThyFunc::BvUrem => write!(f, "BitVec.umod"),
208 ThyFunc::BvSge => write!(f, "BitVec_sge"),
209 ThyFunc::BvSgt => write!(f, "BitVec_sgt"),
210 ThyFunc::BvUge => write!(f, "BitVec_uge"),
211 ThyFunc::BvUgt => write!(f, "BitVec_ugt"),
212 ThyFunc::MapDefault => write!(f, "SmtMap_default"),
213 ThyFunc::MapSelect => write!(f, "SmtMap_select"),
214 ThyFunc::MapStore => write!(f, "SmtMap_store"),
215 func => panic!("Unsupported theory function {}", func),
216 }
217 }
218}
219
220impl<T: LeanFmt> LeanFmt for &T {
221 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
222 (*self).lean_fmt(f, cx)
223 }
224}
225
226struct LeanAdt(AdtId);
227struct LeanDataProj(AdtId, u32);
228struct LeanField(AdtId, u32);
229
230impl LeanFmt for LeanField {
231 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
232 let adt_id = self.0;
233 if let Some(def_id) = cx.adt_map.get_index(adt_id.as_usize())
234 && let Ok(adt_sort_def) = cx.genv.adt_sort_def_of(def_id)
235 {
236 write!(
237 f,
238 "{}",
239 sanitize_name(
240 adt_sort_def.struct_variant().field_names()[self.1 as usize].as_str()
241 )
242 )
243 } else {
244 write!(f, "fld{}", as_subscript(self.1 as usize))
245 }
246 }
247}
248
249impl LeanFmt for LeanAdt {
250 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
251 let def_id = cx.adt_map.get_index(self.0.as_usize()).unwrap();
252 write!(f, "{}", def_id_to_pascal_case(def_id, &cx.genv.tcx()))
253 }
254}
255
256impl LeanFmt for LeanDataProj {
257 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
258 write!(
259 f,
260 "{}.{}",
261 WithLeanCtxt { item: LeanAdt(self.0), cx },
262 WithLeanCtxt { item: LeanField(self.0, self.1), cx }
263 )
264 }
265}
266
267impl LeanFmt for Var {
268 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
269 match self {
270 Var::Global(_gvar, def_id) => {
271 let path = cx
272 .genv
273 .tcx()
274 .def_path(def_id.parent())
275 .to_filename_friendly_no_crate();
276 if path.is_empty() {
277 write!(f, "{}", def_id.name())
278 } else {
279 write!(f, "{}_{}", sanitize_name(&path), def_id.name())
280 }
281 }
282 Var::Const(gvar, None) => {
283 if let Some(stable) = cx.primop_var_map.get(gvar) {
284 write!(f, "{stable}")
285 } else {
286 write!(f, "{}", sanitize_name(&self.display().to_string()))
287 }
288 }
289 Var::Const(_, Some(did)) => {
290 let path = cx.genv.tcx().def_path(*did).to_filename_friendly_no_crate();
291 write!(f, "{}", sanitize_name(&path))
292 }
293 Var::DataCtor(adt_id, idx) => {
294 write!(
295 f,
296 "{}.mk{}{}",
297 WithLeanCtxt { item: LeanAdt(*adt_id), cx },
298 WithLeanCtxt { item: &DataSort::Adt(*adt_id), cx },
299 as_subscript(idx.as_usize())
300 )
301 }
302 Var::DataProj { adt_id, field } => LeanDataProj(*adt_id, *field).lean_fmt(f, cx),
303 Var::Local(local_var) => {
304 write!(f, "{}", cx.pretty_var_map.get(&PrettyVar::Local(*local_var)))
305 }
306 Var::Param(param) => {
307 write!(f, "{}", cx.pretty_var_map.get(&PrettyVar::Param(*param)))
308 }
309 _ => {
310 write!(f, "{}", sanitize_name(&self.display().to_string()))
311 }
312 }
313 }
314}
315
316impl LeanFmt for Sort {
317 fn lean_fmt(&self, f: &mut std::fmt::Formatter, cx: &LeanCtxt) -> std::fmt::Result {
318 match self {
319 Sort::Int => write!(f, "Int"),
320 Sort::Bool => write!(f, "Prop"),
321 Sort::Real => write!(f, "Real"),
322 Sort::Str => write!(f, "String"),
323 Sort::Func(f_sort) => {
324 write!(
325 f,
326 "({} -> {})",
327 WithLeanCtxt { item: &f_sort[0], cx },
328 WithLeanCtxt { item: &f_sort[1], cx }
329 )
330 }
331 Sort::App(sort_ctor, args) => {
332 match sort_ctor {
333 SortCtor::Data(sort) => {
334 if args.is_empty() {
335 sort.lean_fmt(f, cx)
336 } else {
337 write!(
338 f,
339 "({} {})",
340 WithLeanCtxt { item: sort, cx },
341 args.iter()
342 .map(|arg| { WithLeanCtxt { item: arg, cx } })
343 .format(" ")
344 )
345 }
346 }
347 SortCtor::Map => {
348 write!(
349 f,
350 "(SmtMap {} {})",
351 WithLeanCtxt { item: &args[0], cx },
352 WithLeanCtxt { item: &args[1], cx }
353 )
354 }
355 _ => todo!(),
356 }
357 }
358 Sort::BitVec(bv_size) => {
359 match bv_size.as_ref() {
360 Sort::BvSize(size) => write!(f, "BitVec {}", size),
361 s => {
362 panic!(
363 "encountered sort {} where bitvec size was expected",
364 WithLeanCtxt { item: s, cx }
365 )
366 }
367 }
368 }
369 Sort::Abs(v, sort) => {
370 write!(
371 f,
372 "{{t{v} : Type}} -> [Inhabited t{v}] -> {}",
373 WithLeanCtxt { item: sort.as_ref(), cx }
374 )
375 }
376 Sort::Var(v) => {
377 if cx.hide_sort_vars {
378 write!(f, "_")
379 } else {
380 write!(f, "t{v}")
381 }
382 }
383 Sort::BvSize(size) => {
384 panic!("sort BvSize({size}) should only occur as an argument to BitVec")
385 }
386 }
387 }
388}
389
390impl LeanFmt for Expr {
391 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
392 match self {
393 Expr::Var(v) => v.lean_fmt(f, cx),
394 Expr::Constant(c) => {
395 match c {
396 Constant::Numeral(n) => write!(f, "{n}",),
397 Constant::Boolean(b) => write!(f, "{}", if *b { "True" } else { "False" }),
398 Constant::String(s) => write!(f, "{}", s.display()),
399 Constant::Real(n) => write!(f, "{}", n.display()),
400 Constant::BitVec(bv, size) => write!(f, "{}#{}", bv, size),
401 }
402 }
403 Expr::BinaryOp(bin_op, args) => {
404 let bin_op_str = match bin_op {
405 BinOp::Add => "+",
406 BinOp::Sub => "-",
407 BinOp::Mul => "*",
408 BinOp::Div => "/",
409 BinOp::Mod => "%",
410 };
411 write!(f, "(")?;
412 args[0].lean_fmt(f, cx)?;
413 write!(f, " {} ", bin_op_str)?;
414 args[1].lean_fmt(f, cx)?;
415 write!(f, ")")
416 }
417 Expr::Atom(bin_rel, args) => {
418 let bin_rel_str = match bin_rel {
419 BinRel::Eq => "=",
420 BinRel::Ne => "≠",
421 BinRel::Le => "≤",
422 BinRel::Lt => "<",
423 BinRel::Ge => "≥",
424 BinRel::Gt => ">",
425 };
426 write!(f, "(")?;
427 args[0].lean_fmt(f, cx)?;
428 write!(f, " {} ", bin_rel_str)?;
429 args[1].lean_fmt(f, cx)?;
430 write!(f, ")")
431 }
432 Expr::App(function, sort_args, args, out_sort) => {
433 if out_sort.is_some() {
434 write!(f, "(")?;
435 }
436 write!(f, "(")?;
437 function.as_ref().lean_fmt(f, cx)?;
438 if let Some(sort_args) = sort_args {
439 let cx = &LeanCtxt { hide_sort_vars: true, ..*cx };
440 for (i, s_arg) in sort_args.iter().enumerate() {
441 if matches!(s_arg, Sort::BvSize(..)) {
442 continue;
443 }
444 write!(f, " (t{i} := {})", WithLeanCtxt { item: s_arg, cx })?;
445 }
446 }
447 for arg in args {
448 write!(f, " ")?;
449 arg.lean_fmt(f, cx)?;
450 }
451 write!(f, ")")?;
452 if let Some(out_sort) = out_sort {
453 write!(f, " : (")?;
454 out_sort.lean_fmt(f, cx)?;
455 write!(f, "))")?;
456 }
457 Ok(())
458 }
459 Expr::And(exprs) => {
460 write!(f, "(")?;
461 for (i, expr) in exprs.iter().enumerate() {
462 if i > 0 {
463 write!(f, " ∧ ")?;
464 }
465 expr.lean_fmt(f, cx)?;
466 }
467 write!(f, ")")
468 }
469 Expr::Or(exprs) => {
470 write!(f, "(")?;
471 for (i, expr) in exprs.iter().enumerate() {
472 if i > 0 {
473 write!(f, " ∨ ")?;
474 }
475 expr.lean_fmt(f, cx)?;
476 }
477 write!(f, ")")
478 }
479 Expr::Neg(inner) => {
480 write!(f, "(-")?;
481 inner.as_ref().lean_fmt(f, cx)?;
482 write!(f, ")")
483 }
484 Expr::IfThenElse(ite) => {
485 let [condition, if_true, if_false] = ite.as_ref();
486 write!(f, "(if ")?;
487 condition.lean_fmt(f, cx)?;
488 write!(f, " then ")?;
489 if_true.lean_fmt(f, cx)?;
490 write!(f, " else ")?;
491 if_false.lean_fmt(f, cx)?;
492 write!(f, ")")
493 }
494 Expr::Not(inner) => {
495 write!(f, "(")?;
496 write!(f, "¬")?;
497 inner.as_ref().lean_fmt(f, cx)?;
498 write!(f, ")")
499 }
500 Expr::Imp(implication) => {
501 let [lhs, rhs] = implication.as_ref();
502 write!(f, "(")?;
503 lhs.lean_fmt(f, cx)?;
504 write!(f, " -> ")?;
505 rhs.lean_fmt(f, cx)?;
506 write!(f, ")")
507 }
508 Expr::Iff(equiv) => {
509 let [lhs, rhs] = equiv.as_ref();
510 write!(f, "(")?;
511 lhs.lean_fmt(f, cx)?;
512 write!(f, " <-> ")?;
513 rhs.lean_fmt(f, cx)?;
514 write!(f, ")")
515 }
516 Expr::Let(binder, exprs) => {
517 let [def, body] = exprs.as_ref();
518 write!(f, "(let ")?;
519 binder.lean_fmt(f, cx)?;
520 write!(f, " := ")?;
521 def.lean_fmt(f, cx)?;
522 write!(f, "; ")?;
523 body.lean_fmt(f, cx)?;
524 write!(f, ")")
525 }
526 Expr::ThyFunc(thy_func) => {
527 write!(f, "{}", LeanThyFunc(thy_func))
528 }
529 Expr::IsCtor(..) => {
530 todo!("not yet implemented: datatypes in lean")
531 }
532 Expr::Quantifier(q, bind, expr) => {
533 match q {
534 Quantifier::Exists => write!(f, "(∃ ")?,
535 Quantifier::Forall => write!(f, "(∀ ")?,
536 };
537 for (var, sort) in bind {
538 write!(f, "(")?;
539 var.lean_fmt(f, cx)?;
540 write!(f, " : {})", WithLeanCtxt { item: sort, cx })?;
541 }
542 write!(f, ", ")?;
543 expr.lean_fmt(f, cx)?;
544 write!(f, ")")?;
545 Ok(())
546 }
547 }
548 }
549}
550
551impl LeanFmt for FunDef {
552 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
553 let FunDef { name, sort, comment: _, body } = self;
554 write!(f, "noncomputable def ")?;
555 name.lean_fmt(f, cx)?;
556 if let Some(body) = body {
557 for (arg, arg_sort) in iter::zip(&body.args, &sort.inputs) {
558 write!(f, " (")?;
559 arg.lean_fmt(f, cx)?;
560 write!(f, " : {})", WithLeanCtxt { item: arg_sort, cx })?;
561 }
562 writeln!(f, " : {} :=", WithLeanCtxt { item: &sort.output, cx })?;
563 write!(f, " ")?;
564 body.expr.lean_fmt(f, cx)?;
565 } else {
566 write!(f, " : {} := sorry", WithLeanCtxt { item: sort, cx })?;
567 }
568 writeln!(f)
569 }
570}
571
572impl LeanFmt for FunSort {
573 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
574 for i in 0..self.params {
575 write!(f, "{{t{i} : Type}} -> [Inhabited t{i}] -> ")?;
576 }
577 if self.inputs.is_empty() {
578 write!(f, "{}", WithLeanCtxt { item: &self.output, cx })
579 } else {
580 write!(
581 f,
582 "{} -> {}",
583 self.inputs.iter().format_with(" -> ", |sort, f| {
584 f(&format_args!("{}", WithLeanCtxt { item: sort, cx }))
585 }),
586 WithLeanCtxt { item: &self.output, cx }
587 )
588 }
589 }
590}
591
592impl LeanFmt for Pred {
593 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
594 match self {
595 Pred::Expr(expr) => expr.lean_fmt(f, cx),
596 Pred::And(preds) => {
597 write!(f, "(")?;
598 for (i, pred) in preds.iter().enumerate() {
599 if i > 0 {
600 write!(f, " ∧ ")?;
601 }
602 pred.lean_fmt(f, cx)?;
603 }
604 write!(f, ")")
605 }
606 Pred::KVar(kvid, args) => {
607 write!(f, "({}", sanitize_name(&kvid.display().to_string()))?;
608 for arg in args {
609 write!(f, " ")?;
610 arg.lean_fmt(f, cx)?;
611 }
612 write!(f, ")")
613 }
614 }
615 }
616}
617
618impl LeanFmt for KVarDecl {
619 fn lean_fmt(&self, f: &mut std::fmt::Formatter, cx: &LeanCtxt) -> std::fmt::Result {
620 let sorts = self
621 .sorts
622 .iter()
623 .enumerate()
624 .map(|(i, sort)| format!("(a{i} : {})", WithLeanCtxt { item: sort, cx }))
625 .format(" -> ");
626 write!(f, "∃ {} : {} -> Prop", sanitize_name(&self.kvid.display().to_string()), sorts)
627 }
628}
629
630impl LeanFmt for (&KVid, &ClosedSolution) {
631 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
632 let (kvid, (implicit, (explicit, inner))) = self;
633 write!(f, "def k{} ", kvid.as_usize())?;
634 for (arg, sort) in implicit.iter().chain(explicit) {
635 write!(f, "(")?;
636 arg.lean_fmt(f, cx)?;
637 write!(f, " : {}) ", WithLeanCtxt { item: sort, cx })?;
638 }
639 writeln!(f, ": Prop :=")?;
640 write!(f, " ")?;
641 inner.lean_fmt(f, cx)?;
642 Ok(())
643 }
644}
645
646impl<'a> LeanFmt for LeanKConstraint<'a> {
647 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
648 let theorem_name = self.theorem_name.replace(".", "_");
649
650 write!(f, "\n\ndef {theorem_name} := ")?;
651
652 if self.should_fail {
653 write!(f, "¬")?;
654 }
655
656 if self.kvars.is_empty() {
657 self.constr.lean_fmt(f, cx)
658 } else {
659 write!(
660 f,
661 "{}, ",
662 self.kvars
663 .iter()
664 .map(|kvar| { WithLeanCtxt { item: kvar, cx } })
665 .format(", ")
666 )?;
667 self.constr.lean_fmt(f, cx)
668 }
669 }
670}
671
672impl LeanFmt for Constraint {
673 fn lean_fmt(&self, f: &mut fmt::Formatter, cx: &LeanCtxt) -> fmt::Result {
674 let mut fmt_cx = ConstraintFormatter::default();
675 fmt_cx.incr();
676 fmt_cx.newline(f)?;
677 self.fmt_nested(f, cx, &mut fmt_cx)?;
678 fmt_cx.decr();
679 Ok(())
680 }
681}
682
683impl FormatNested for Constraint {
684 fn fmt_nested(
685 &self,
686 f: &mut fmt::Formatter,
687 lean_cx: &LeanCtxt,
688 fmt_cx: &mut ConstraintFormatter,
689 ) -> fmt::Result {
690 match self {
691 Constraint::ForAll(bind, inner) => {
692 let trivial_pred = bind.pred.is_trivially_true();
693 let trivial_bind = bind.name.display().to_string().starts_with("_");
694 if !trivial_bind {
695 write!(f, "∀ (")?;
696 bind.name.lean_fmt(f, lean_cx)?;
697 write!(f, " : {}),", WithLeanCtxt { item: &bind.sort, cx: lean_cx })?;
698 fmt_cx.incr();
699 fmt_cx.newline(f)?;
700 }
701 if !trivial_pred {
702 bind.pred.lean_fmt(f, lean_cx)?;
703 write!(f, " ->")?;
704 fmt_cx.incr();
705 fmt_cx.newline(f)?;
706 }
707 inner.fmt_nested(f, lean_cx, fmt_cx)?;
708 if !trivial_pred {
709 fmt_cx.decr();
710 }
711 if !trivial_bind {
712 fmt_cx.decr();
713 }
714 Ok(())
715 }
716 Constraint::Conj(constraints) => {
717 let n = constraints.len();
718 for (i, constraint) in constraints.iter().enumerate() {
719 write!(f, "(")?;
720 constraint.fmt_nested(f, lean_cx, fmt_cx)?;
721 write!(f, ")")?;
722 if i < n - 1 {
723 write!(f, " ∧")?;
724 }
725 fmt_cx.newline(f)?;
726 }
727 Ok(())
728 }
729 Constraint::Pred(pred, _) => pred.lean_fmt(f, lean_cx),
730 }
731 }
732}
733
734pub trait FormatNested {
735 fn fmt_nested(
736 &self,
737 f: &mut fmt::Formatter,
738 lean_cx: &LeanCtxt,
739 fmt_cx: &mut ConstraintFormatter,
740 ) -> fmt::Result;
741}
742
743#[derive(Default)]
744pub struct ConstraintFormatter {
745 level: u32,
746}
747
748impl ConstraintFormatter {
749 pub fn incr(&mut self) {
750 self.level += 1;
751 }
752
753 pub fn decr(&mut self) {
754 self.level -= 1;
755 }
756
757 pub fn newline(&self, f: &mut fmt::Formatter) -> fmt::Result {
758 f.write_char('\n')?;
759 self.padding(f)
760 }
761
762 pub fn padding(&self, f: &mut fmt::Formatter) -> fmt::Result {
763 for _ in 0..self.level {
764 f.write_str(" ")?;
765 }
766 Ok(())
767 }
768}
769
770static IMPL_RE: LazyLock<regex::Regex> =
771 std::sync::LazyLock::new(|| regex::Regex::new(r"\{impl#(\d+)\}").unwrap());
772
773fn is_reserved(name: &str) -> bool {
774 matches!(name, "end" | "def")
775}
776
777fn sanitize_name(name: &str) -> String {
778 if is_reserved(name) {
779 format!("{name}_")
780 } else {
781 IMPL_RE
782 .replace_all(name, "impl_${1}")
783 .replace("-", "_")
784 .replace("$", "_")
785 }
786}
787
788pub fn def_id_to_pascal_case(def_id: &DefId, tcx: &rustc_middle::ty::TyCtxt) -> String {
789 let snake = tcx
790 .def_path(*def_id)
791 .to_filename_friendly_no_crate()
792 .replace("-", "_");
793 let pascal_case = snake_case_to_pascal_case(&snake);
794 IMPL_RE
795 .replace_all(&pascal_case, "Impl__${1}__")
796 .to_string()
797}
798
799pub fn snake_case_to_pascal_case(snake: &str) -> String {
800 snake
801 .split('_')
802 .filter(|s| !s.is_empty()) .map(|word| {
804 let mut chars = word.chars();
805 match chars.next() {
806 Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
807 None => String::new(),
808 }
809 })
810 .collect::<String>()
811}