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