Skip to main content

flux_middle/rty/
expr.rs

1use std::{fmt, hash::Hash, iter, ops::ControlFlow, sync::OnceLock};
2
3use flux_arc_interner::{Interned, List, impl_internable, impl_slice_internable};
4use flux_common::{bug, dbg::as_subscript};
5use flux_macros::{TypeFoldable, TypeVisitable};
6use flux_rustc_bridge::{
7    ToRustc,
8    const_eval::{scalar_to_bits, scalar_to_int, scalar_to_uint},
9    ty::{Const, ConstKind, ValTree, VariantIdx},
10};
11use flux_syntax::symbols::sym;
12use itertools::Itertools;
13use liquid_fixpoint::ThyFunc;
14use rustc_abi::{FIRST_VARIANT, FieldIdx};
15use rustc_data_structures::{snapshot_map::SnapshotMap, unord::UnordMap};
16use rustc_hir::def_id::DefId;
17use rustc_index::newtype_index;
18use rustc_macros::{Decodable, Encodable, TyDecodable, TyEncodable};
19use rustc_middle::{
20    mir::Local,
21    ty::{ParamConst, ScalarInt, TyCtxt},
22};
23use rustc_span::{Span, Symbol};
24use rustc_type_ir::{BoundVar, DebruijnIndex, INNERMOST};
25
26use super::{
27    BaseTy, Binder, BoundReftKind, BoundVariableKinds, FuncSort, GenericArgs, GenericArgsExt as _,
28    IntTy, Sort, UintTy,
29};
30use crate::{
31    big_int::BigInt,
32    def_id::FluxDefId,
33    fhir::{self},
34    global_env::GlobalEnv,
35    pretty::*,
36    queries::QueryResult,
37    rty::{
38        BoundVariableKind, SortArg, SortCtor, SubsetTyCtor,
39        fold::{
40            TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable,
41            TypeVisitor,
42        },
43    },
44};
45
46/// A lambda abstraction with an elaborated output sort. We need the output sort of lambdas for
47/// encoding into fixpoint
48#[derive(Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)]
49pub struct Lambda {
50    body: Binder<Expr>,
51    output: Sort,
52}
53
54impl Lambda {
55    pub fn bind_with_vars(body: Expr, inputs: BoundVariableKinds, output: Sort) -> Self {
56        debug_assert!(inputs.iter().all(BoundVariableKind::is_refine));
57        Self { body: Binder::bind_with_vars(body, inputs), output }
58    }
59
60    pub fn bind_with_fsort(body: Expr, fsort: FuncSort) -> Self {
61        Self { body: Binder::bind_with_sorts(body, fsort.inputs()), output: fsort.output().clone() }
62    }
63
64    pub fn apply(&self, args: &[Expr]) -> Expr {
65        self.body.replace_bound_refts(args)
66    }
67
68    pub fn vars(&self) -> &BoundVariableKinds {
69        self.body.vars()
70    }
71
72    pub fn output(&self) -> Sort {
73        self.output.clone()
74    }
75
76    pub fn fsort(&self) -> FuncSort {
77        let inputs_and_output = self
78            .vars()
79            .iter()
80            .map(|kind| kind.expect_sort().clone())
81            .chain(iter::once(self.output()))
82            .collect();
83        FuncSort { inputs_and_output }
84    }
85}
86
87#[derive(Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)]
88pub struct AliasReft {
89    /// Id of the associated refinement in the trait
90    pub assoc_id: FluxDefId,
91    pub args: GenericArgs,
92}
93
94impl AliasReft {
95    pub fn self_ty(&self) -> SubsetTyCtor {
96        self.args[0].expect_base().clone()
97    }
98
99    pub fn to_rustc_trait_ref<'tcx>(&self, tcx: TyCtxt<'tcx>) -> rustc_middle::ty::TraitRef<'tcx> {
100        let trait_def_id = self.assoc_id.parent();
101        let args = self
102            .args
103            .to_rustc(tcx)
104            .truncate_to(tcx, tcx.generics_of(trait_def_id));
105        rustc_middle::ty::TraitRef::new(tcx, trait_def_id, args)
106    }
107}
108
109#[derive(Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable)]
110pub struct Expr {
111    kind: Interned<ExprKind>,
112    espan: Option<ESpan>,
113}
114
115impl Expr {
116    pub fn at_opt(self, espan: Option<ESpan>) -> Expr {
117        Expr { kind: self.kind, espan }
118    }
119
120    pub fn at(self, espan: ESpan) -> Expr {
121        self.at_opt(Some(espan))
122    }
123
124    pub fn at_base(self, base: ESpan) -> Expr {
125        if let Some(espan) = self.espan { self.at(espan.with_base(base)) } else { self }
126    }
127
128    pub fn span(&self) -> Option<ESpan> {
129        self.espan
130    }
131
132    pub fn tt() -> Expr {
133        static TRUE: OnceLock<Expr> = OnceLock::new();
134        TRUE.get_or_init(|| ExprKind::Constant(Constant::Bool(true)).intern())
135            .clone()
136    }
137
138    pub fn ff() -> Expr {
139        static FALSE: OnceLock<Expr> = OnceLock::new();
140        FALSE
141            .get_or_init(|| ExprKind::Constant(Constant::Bool(false)).intern())
142            .clone()
143    }
144
145    pub fn and_from_iter(exprs: impl IntoIterator<Item = Expr>) -> Expr {
146        exprs
147            .into_iter()
148            .reduce(|acc, e| Expr::binary_op(BinOp::And, acc, e))
149            .unwrap_or_else(Expr::tt)
150    }
151
152    pub fn or_from_iter(exprs: impl IntoIterator<Item = Expr>) -> Expr {
153        exprs
154            .into_iter()
155            .reduce(|acc, e| Expr::binary_op(BinOp::Or, acc, e))
156            .unwrap_or_else(Expr::ff)
157    }
158
159    pub fn and(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
160        Expr::and_from_iter([e1.into(), e2.into()])
161    }
162
163    pub fn or(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
164        Expr::or_from_iter([e1.into(), e2.into()])
165    }
166
167    pub fn zero() -> Expr {
168        static ZERO: OnceLock<Expr> = OnceLock::new();
169        ZERO.get_or_init(|| ExprKind::Constant(Constant::ZERO).intern())
170            .clone()
171    }
172
173    pub fn int_max(int_ty: IntTy) -> Expr {
174        let bit_width: u64 = int_ty
175            .bit_width()
176            .unwrap_or(flux_config::pointer_width().bits());
177        Expr::constant(Constant::int_max(bit_width.try_into().unwrap()))
178    }
179
180    pub fn int_min(int_ty: IntTy) -> Expr {
181        let bit_width: u64 = int_ty
182            .bit_width()
183            .unwrap_or(flux_config::pointer_width().bits());
184        Expr::constant(Constant::int_min(bit_width.try_into().unwrap()))
185    }
186
187    pub fn uint_max(uint_ty: UintTy) -> Expr {
188        let bit_width: u64 = uint_ty
189            .bit_width()
190            .unwrap_or(flux_config::pointer_width().bits());
191        Expr::constant(Constant::uint_max(bit_width.try_into().unwrap()))
192    }
193
194    pub fn nu() -> Expr {
195        Expr::bvar(INNERMOST, BoundVar::ZERO, BoundReftKind::Anon)
196    }
197
198    pub fn is_nu(&self) -> bool {
199        if let ExprKind::Var(Var::Bound(INNERMOST, var)) = self.kind()
200            && var.var == BoundVar::ZERO
201        {
202            true
203        } else {
204            false
205        }
206    }
207
208    pub fn unit() -> Expr {
209        Expr::tuple(List::empty())
210    }
211
212    pub fn var(var: Var) -> Expr {
213        ExprKind::Var(var).intern()
214    }
215
216    pub fn fvar(name: Name) -> Expr {
217        Var::Free(name).to_expr()
218    }
219
220    pub fn evar(evid: EVid) -> Expr {
221        Var::EVar(evid).to_expr()
222    }
223
224    pub fn bvar(debruijn: DebruijnIndex, var: BoundVar, kind: BoundReftKind) -> Expr {
225        Var::Bound(debruijn, BoundReft { var, kind }).to_expr()
226    }
227
228    pub fn early_param(index: u32, name: Symbol) -> Expr {
229        Var::EarlyParam(EarlyReftParam { index, name }).to_expr()
230    }
231
232    pub fn local(local: Local) -> Expr {
233        ExprKind::Local(local).intern()
234    }
235
236    pub fn constant(c: Constant) -> Expr {
237        ExprKind::Constant(c).intern()
238    }
239
240    pub fn const_def_id(c: DefId) -> Expr {
241        ExprKind::ConstDefId(c).intern()
242    }
243
244    pub fn const_generic(param: ParamConst) -> Expr {
245        ExprKind::Var(Var::ConstGeneric(param)).intern()
246    }
247
248    pub fn tuple(flds: List<Expr>) -> Expr {
249        ExprKind::Tuple(flds).intern()
250    }
251
252    pub fn ctor_struct(def_id: DefId, flds: List<Expr>) -> Expr {
253        ExprKind::Ctor(Ctor::Struct(def_id), flds).intern()
254    }
255
256    pub fn ctor_raw_ptr(flds: List<Expr>) -> Expr {
257        ExprKind::Ctor(Ctor::RawPtr, flds).intern()
258    }
259
260    pub fn ctor_enum(def_id: DefId, idx: VariantIdx) -> Expr {
261        ExprKind::Ctor(Ctor::Enum(def_id, idx), List::empty()).intern()
262    }
263
264    pub fn ctor(ctor: Ctor, flds: List<Expr>) -> Expr {
265        ExprKind::Ctor(ctor, flds).intern()
266    }
267
268    pub fn is_ctor(def_id: DefId, variant_idx: VariantIdx, idx: impl Into<Expr>) -> Expr {
269        ExprKind::IsCtor(def_id, variant_idx, idx.into()).intern()
270    }
271
272    pub fn from_bits(bty: &BaseTy, bits: u128) -> Expr {
273        // FIXME: We are assuming the higher bits are not set. check this assumption
274        match bty {
275            BaseTy::Int(_) => {
276                let bits = bits as i128;
277                ExprKind::Constant(Constant::from(bits)).intern()
278            }
279            BaseTy::Uint(_) => ExprKind::Constant(Constant::from(bits)).intern(),
280            BaseTy::Bool => ExprKind::Constant(Constant::Bool(bits != 0)).intern(),
281            BaseTy::Char => {
282                let c = char::from_u32(bits.try_into().unwrap()).unwrap();
283                ExprKind::Constant(Constant::Char(c)).intern()
284            }
285            _ => bug!(),
286        }
287    }
288
289    pub fn ite(p: impl Into<Expr>, e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
290        ExprKind::IfThenElse(p.into(), e1.into(), e2.into()).intern()
291    }
292
293    fn empty() -> Expr {
294        let func = Self::global_func(SpecFuncKind::Thy(ThyFunc::SetEmpty));
295        Expr::app(func, List::empty(), List::from_arr([Expr::zero()]))
296    }
297
298    fn singleton(elem: Expr) -> Expr {
299        let func = Self::global_func(SpecFuncKind::Thy(ThyFunc::SetSng));
300        Expr::app(func, List::empty(), List::from_arr([elem]))
301    }
302
303    fn union(expr1: Expr, expr2: Expr) -> Expr {
304        let func = Self::global_func(SpecFuncKind::Thy(ThyFunc::SetCup));
305        Expr::app(func, List::empty(), List::from_arr([expr1, expr2]))
306    }
307
308    pub fn set(elems: List<Expr>) -> Expr {
309        let mut expr = Expr::empty();
310        for elem in &elems {
311            expr = Self::union(expr, Self::singleton(elem.clone()));
312        }
313        expr
314    }
315
316    pub fn abs(lam: Lambda) -> Expr {
317        ExprKind::Abs(lam).intern()
318    }
319
320    pub fn let_(init: Expr, body: Binder<Expr>) -> Expr {
321        ExprKind::Let(init, body).intern()
322    }
323
324    pub fn quant(kind: fhir::QuantKind, dom: QuantDom, body: Binder<Expr>) -> Expr {
325        ExprKind::Quant(kind, dom, body).intern()
326    }
327
328    pub fn hole(kind: HoleKind) -> Expr {
329        ExprKind::Hole(kind).intern()
330    }
331
332    pub fn kvar(kvar: KVar) -> Expr {
333        ExprKind::KVar(kvar).intern()
334    }
335
336    pub fn wkvar(wkvar: WKVar) -> Expr {
337        ExprKind::WKVar(wkvar).intern()
338    }
339
340    pub fn alias(alias: AliasReft, args: List<Expr>) -> Expr {
341        ExprKind::Alias(alias, args).intern()
342    }
343
344    pub fn forall(expr: Binder<Expr>) -> Expr {
345        ExprKind::Quant(fhir::QuantKind::Forall, QuantDom::Unbounded, expr).intern()
346    }
347
348    pub fn exists(expr: Binder<Expr>) -> Expr {
349        ExprKind::Quant(fhir::QuantKind::Exists, QuantDom::Unbounded, expr).intern()
350    }
351
352    pub fn binary_op(op: BinOp, e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
353        ExprKind::BinaryOp(op, e1.into(), e2.into()).intern()
354    }
355
356    pub fn prim_rel(op: BinOp, e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
357        Expr::app(InternalFuncKind::Rel(op), List::empty(), List::from_arr([e1.into(), e2.into()]))
358    }
359
360    pub fn unit_struct(def_id: DefId) -> Expr {
361        Expr::ctor_struct(def_id, List::empty())
362    }
363
364    pub fn cast(from: Sort, to: Sort, idx: Expr) -> Expr {
365        Expr::app(
366            InternalFuncKind::Cast,
367            List::from_arr([SortArg::Sort(from), SortArg::Sort(to)]),
368            List::from_arr([idx]),
369        )
370    }
371
372    pub fn app(func: impl Into<Expr>, sort_args: List<SortArg>, args: List<Expr>) -> Expr {
373        ExprKind::App(func.into(), sort_args, args).intern()
374    }
375
376    pub fn global_func(kind: SpecFuncKind) -> Expr {
377        ExprKind::GlobalFunc(kind).intern()
378    }
379
380    pub fn internal_func(kind: InternalFuncKind) -> Expr {
381        ExprKind::InternalFunc(kind).intern()
382    }
383
384    pub fn eq(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
385        ExprKind::BinaryOp(BinOp::Eq, e1.into(), e2.into()).intern()
386    }
387
388    pub fn unary_op(op: UnOp, e: impl Into<Expr>) -> Expr {
389        ExprKind::UnaryOp(op, e.into()).intern()
390    }
391
392    pub fn ne(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
393        ExprKind::BinaryOp(BinOp::Ne, e1.into(), e2.into()).intern()
394    }
395
396    pub fn ge(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
397        ExprKind::BinaryOp(BinOp::Ge(Sort::Int), e1.into(), e2.into()).intern()
398    }
399
400    pub fn gt(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
401        ExprKind::BinaryOp(BinOp::Gt(Sort::Int), e1.into(), e2.into()).intern()
402    }
403
404    pub fn lt(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
405        ExprKind::BinaryOp(BinOp::Lt(Sort::Int), e1.into(), e2.into()).intern()
406    }
407
408    pub fn le(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
409        ExprKind::BinaryOp(BinOp::Le(Sort::Int), e1.into(), e2.into()).intern()
410    }
411
412    pub fn implies(e1: impl Into<Expr>, e2: impl Into<Expr>) -> Expr {
413        ExprKind::BinaryOp(BinOp::Imp, e1.into(), e2.into()).intern()
414    }
415
416    pub fn field_proj(e: impl Into<Expr>, proj: FieldProj) -> Expr {
417        ExprKind::FieldProj(e.into(), proj).intern()
418    }
419
420    pub fn field_projs(e: impl Into<Expr>, projs: &[FieldProj]) -> Expr {
421        projs.iter().copied().fold(e.into(), Expr::field_proj)
422    }
423
424    pub fn path_proj(base: Expr, field: FieldIdx) -> Expr {
425        ExprKind::PathProj(base, field).intern()
426    }
427
428    pub fn not(&self) -> Expr {
429        ExprKind::UnaryOp(UnOp::Not, self.clone()).intern()
430    }
431
432    pub fn neg(&self) -> Expr {
433        ExprKind::UnaryOp(UnOp::Neg, self.clone()).intern()
434    }
435
436    pub fn kind(&self) -> &ExprKind {
437        &self.kind
438    }
439
440    /// An expression is an *atom* if it is "self-delimiting", i.e., it has a clear boundary
441    /// when printed. This is used to avoid unnecessary parenthesis when pretty printing.
442    pub fn is_atom(&self) -> bool {
443        !matches!(self.kind(), ExprKind::Abs(..) | ExprKind::BinaryOp(..) | ExprKind::Quant(..))
444    }
445
446    /// Simple syntactic check to see if the expression is a trivially true predicate. This is used
447    /// mostly for filtering predicates when pretty printing but also to simplify types in general.
448    pub fn is_trivially_true(&self) -> bool {
449        self.is_true()
450            || matches!(self.kind(), ExprKind::BinaryOp(BinOp::Eq | BinOp::Iff | BinOp::Imp, e1, e2) if e1.erase_spans() == e2.erase_spans())
451    }
452
453    /// Simple syntactic check to see if the expression is a trivially false predicate.
454    pub fn is_trivially_false(&self) -> bool {
455        self.is_false()
456    }
457
458    /// Whether the expression is *literally* the constant `true`.
459    fn is_true(&self) -> bool {
460        matches!(self.kind(), ExprKind::Constant(Constant::Bool(true)))
461    }
462
463    /// Whether the expression is *literally* the constant `false`.
464    fn is_false(&self) -> bool {
465        matches!(self.kind(), ExprKind::Constant(Constant::Bool(false)))
466    }
467
468    pub fn from_const(tcx: TyCtxt, c: &Const) -> Expr {
469        match &c.kind {
470            ConstKind::Param(param_const) => Expr::const_generic(*param_const),
471            ConstKind::Value(ty, ValTree::Leaf(scalar)) => {
472                Expr::constant(Constant::from_scalar_int(tcx, *scalar, ty).unwrap())
473            }
474            ConstKind::Value(_ty, ValTree::Branch(_)) => {
475                bug!("todo: ValTree::Branch {c:?}")
476            }
477            // We should have normalized away the unevaluated constants
478            ConstKind::Alias(_) => bug!("unexpected `ConstKind::Alias`"),
479
480            ConstKind::Infer(_) => bug!("unexpected `ConstKind::Infer`"),
481        }
482    }
483
484    fn const_op(op: &BinOp, c1: &Constant, c2: &Constant) -> Option<Constant> {
485        match op {
486            BinOp::Iff => c1.iff(c2),
487            BinOp::Imp => c1.imp(c2),
488            BinOp::Or => c1.or(c2),
489            BinOp::And => c1.and(c2),
490            BinOp::Gt(Sort::Int) => c1.gt(c2),
491            BinOp::Ge(Sort::Int) => c1.ge(c2),
492            BinOp::Lt(Sort::Int) => c2.gt(c1),
493            BinOp::Le(Sort::Int) => c2.ge(c1),
494            BinOp::Eq => Some(c1.eq(c2)),
495            BinOp::Ne => Some(c1.ne(c2)),
496            _ => None,
497        }
498    }
499
500    /// Simplify the expression by removing double negations, short-circuiting boolean connectives and
501    /// doing constant folding. Note that we also have [`TypeFoldable::reduce`] which inlines spec
502    /// functions and reduces applications and projections.
503    ///
504    /// Additionally replaces any occurrences of elements in assumed_preds with True.
505    pub fn simplify(&self, assumed_preds: &SnapshotMap<Expr, ()>) -> Expr {
506        struct Simplify<'a> {
507            assumed_preds: &'a SnapshotMap<Expr, ()>,
508        }
509
510        impl TypeFolder for Simplify<'_> {
511            fn fold_expr(&mut self, expr: &Expr) -> Expr {
512                if self.assumed_preds.get(&expr.erase_spans()).is_some() {
513                    return Expr::tt();
514                }
515                let span = expr.span();
516                match expr.kind() {
517                    ExprKind::BinaryOp(op, e1, e2) => {
518                        let e1 = e1.fold_with(self);
519                        let e2 = e2.fold_with(self);
520                        match (op, e1.kind(), e2.kind()) {
521                            (BinOp::And, ExprKind::Constant(Constant::Bool(false)), _) => {
522                                Expr::constant(Constant::Bool(false)).at_opt(e1.span())
523                            }
524                            (BinOp::And, _, ExprKind::Constant(Constant::Bool(false))) => {
525                                Expr::constant(Constant::Bool(false)).at_opt(e2.span())
526                            }
527                            (BinOp::And, ExprKind::Constant(Constant::Bool(true)), _) => e2,
528                            (BinOp::And, _, ExprKind::Constant(Constant::Bool(true))) => e1,
529                            (op, ExprKind::Constant(c1), ExprKind::Constant(c2)) => {
530                                if let Some(c) = Expr::const_op(op, c1, c2) {
531                                    Expr::constant(c).at_opt(span.or(e2.span()))
532                                } else {
533                                    Expr::binary_op(op.clone(), e1, e2).at_opt(span)
534                                }
535                            }
536                            _ => Expr::binary_op(op.clone(), e1, e2).at_opt(span),
537                        }
538                    }
539                    ExprKind::UnaryOp(UnOp::Not, e) => {
540                        let e = e.fold_with(self);
541                        match e.kind() {
542                            ExprKind::Constant(Constant::Bool(b)) => {
543                                Expr::constant(Constant::Bool(!b))
544                            }
545                            ExprKind::UnaryOp(UnOp::Not, e) => e.clone(),
546                            ExprKind::BinaryOp(BinOp::Eq, e1, e2) => {
547                                Expr::binary_op(BinOp::Ne, e1, e2).at_opt(span)
548                            }
549                            _ => Expr::unary_op(UnOp::Not, e).at_opt(span),
550                        }
551                    }
552                    ExprKind::IfThenElse(p, e1, e2) => {
553                        let p = p.fold_with(self);
554                        if p.is_trivially_true() {
555                            e1.fold_with(self).at_opt(span)
556                        } else if p.is_trivially_false() {
557                            e2.fold_with(self).at_opt(span)
558                        } else {
559                            Expr::ite(p, e1.fold_with(self), e2.fold_with(self)).at_opt(span)
560                        }
561                    }
562                    _ => expr.super_fold_with(self),
563                }
564            }
565        }
566        self.fold_with(&mut Simplify { assumed_preds })
567    }
568
569    pub fn to_loc(&self) -> Option<Loc> {
570        match self.kind() {
571            ExprKind::Local(local) => Some(Loc::Local(*local)),
572            ExprKind::Var(var) => Some(Loc::Var(*var)),
573            _ => None,
574        }
575    }
576
577    pub fn to_path(&self) -> Option<Path> {
578        let mut expr = self;
579        let mut proj = vec![];
580        while let ExprKind::PathProj(e, field) = expr.kind() {
581            proj.push(*field);
582            expr = e;
583        }
584        proj.reverse();
585        Some(Path::new(expr.to_loc()?, proj))
586    }
587
588    /// Whether this is an aggregate expression with no fields.
589    pub fn is_unit(&self) -> bool {
590        matches!(self.kind(), ExprKind::Tuple(flds) if flds.is_empty())
591            || matches!(self.kind(), ExprKind::Ctor(Ctor::Struct(_), flds) if flds.is_empty())
592    }
593
594    pub fn eta_expand_abs(&self, inputs: &BoundVariableKinds, output: Sort) -> Lambda {
595        let args = (0..inputs.len())
596            .map(|idx| Expr::bvar(INNERMOST, BoundVar::from_usize(idx), BoundReftKind::Anon))
597            .collect();
598        let body = Expr::app(self, List::empty(), args);
599        Lambda::bind_with_vars(body, inputs.clone(), output)
600    }
601
602    /// Applies a field projection to an expression and optimistically try to beta reduce it
603    pub fn proj_and_reduce(&self, proj: FieldProj) -> Expr {
604        match self.kind() {
605            ExprKind::Tuple(flds) | ExprKind::Ctor(Ctor::Struct(_) | Ctor::RawPtr, flds) => {
606                flds[proj.field_idx() as usize].clone()
607            }
608            _ => Expr::field_proj(self.clone(), proj),
609        }
610    }
611
612    /// Reduce a pointer to its address, for relational operators
613    pub fn reduce_ptr_addr(&self) -> Expr {
614        self.proj_and_reduce(FieldProj::RawPtr { field: RawPtrField::Addr })
615    }
616
617    pub fn visit_conj<'a>(&'a self, mut f: impl FnMut(&'a Expr)) {
618        fn go<'a>(e: &'a Expr, f: &mut impl FnMut(&'a Expr)) {
619            if let ExprKind::BinaryOp(BinOp::And, e1, e2) = e.kind() {
620                go(e1, f);
621                go(e2, f);
622            } else {
623                f(e);
624            }
625        }
626        go(self, &mut f);
627    }
628
629    pub fn flatten_conjs(&self) -> Vec<&Expr> {
630        let mut vec = vec![];
631        self.visit_conj(|e| vec.push(e));
632        vec
633    }
634
635    pub fn has_evars(&self) -> bool {
636        struct HasEvars;
637
638        impl TypeVisitor for HasEvars {
639            type BreakTy = ();
640            fn visit_expr(&mut self, expr: &Expr) -> ControlFlow<Self::BreakTy> {
641                if let ExprKind::Var(Var::EVar(_)) = expr.kind() {
642                    ControlFlow::Break(())
643                } else {
644                    expr.super_visit_with(self)
645                }
646            }
647        }
648
649        self.visit_with(&mut HasEvars).is_break()
650    }
651
652    pub fn erase_spans(&self) -> Expr {
653        struct SpanEraser;
654        impl TypeFolder for SpanEraser {
655            fn fold_expr(&mut self, e: &Expr) -> Expr {
656                e.super_fold_with(self).at_opt(None)
657            }
658        }
659        self.fold_with(&mut SpanEraser)
660    }
661
662    /// Replaces [`BoundReftKind::Named(..)`] in [`Var::Bound(..)`] with
663    /// [`BoundReftKind::Anon`]. This is to ensure that expr equality works
664    /// properly --- the names are just annotations that do not change the
665    /// expressions themselves.
666    ///
667    /// If you think you need this function, you may be looking for
668    /// [`Expr::erase_metadata()`].
669    pub fn erase_bound_reft_kind(&self) -> Expr {
670        struct BoundReftKindEraser;
671        impl TypeFolder for BoundReftKindEraser {
672            fn fold_expr(&mut self, expr: &Expr) -> Expr {
673                if let ExprKind::Var(Var::Bound(
674                    db_index,
675                    BoundReft { var, kind: BoundReftKind::Named(_) },
676                )) = expr.kind()
677                {
678                    Expr::bvar(*db_index, *var, BoundReftKind::Anon)
679                } else {
680                    expr.super_fold_with(self)
681                }
682            }
683        }
684        self.fold_with(&mut BoundReftKindEraser)
685    }
686
687    pub fn erase_metadata(&self) -> Expr {
688        self.erase_spans().erase_bound_reft_kind()
689    }
690
691    /// Binary relations are lifted to tuples and ADTs, so e.g.
692    ///
693    ///     (1, false) > (0, true)
694    ///
695    /// is treated as
696    ///
697    ///     1 > 0 && false > true
698    ///
699    /// This "expands" the binary relations on tuples and ADTs to the second
700    /// form where their elements are individually compared.
701    ///
702    /// Only some binary relations are lifted in this manner, see
703    /// `bin_op_to_fixpoint` in `fixpoint_encoding.rs`.
704    pub fn expand_bin_rels(&self) -> Expr {
705        struct BinRelExpander;
706        impl BinRelExpander {
707            fn expand_bin_rel(&mut self, sort: &Sort, rel: &BinOp, e1: &Expr, e2: &Expr) -> Expr {
708                match sort {
709                    Sort::Tuple(sorts) => {
710                        let arity = sorts.len();
711                        self.apply_bin_rel_rec(sorts, rel, e1, e2, |field| {
712                            FieldProj::Tuple { arity, field }
713                        })
714                    }
715                    Sort::App(SortCtor::Adt(sort_def), args)
716                        if let Some(variant) = sort_def.opt_struct_variant() =>
717                    {
718                        let def_id = sort_def.did();
719                        let sorts = variant.field_sorts(args);
720                        self.apply_bin_rel_rec(&sorts, rel, e1, e2, |field| {
721                            FieldProj::Adt { def_id, field }
722                        })
723                    }
724                    _ => {
725                        Expr::binary_op(
726                            rel.clone(),
727                            e1.super_fold_with(self),
728                            e2.super_fold_with(self),
729                        )
730                    }
731                }
732            }
733            /// Apply binary relation recursively over aggregate expressions
734            fn apply_bin_rel_rec(
735                &mut self,
736                sorts: &[Sort],
737                rel: &BinOp,
738                e1: &Expr,
739                e2: &Expr,
740                mk_proj: impl Fn(u32) -> FieldProj,
741            ) -> Expr {
742                Expr::and_from_iter(sorts.iter().enumerate().map(|(idx, s)| {
743                    let proj = mk_proj(idx as u32);
744                    let e1 = e1.proj_and_reduce(proj);
745                    let e2 = e2.proj_and_reduce(proj);
746                    self.expand_bin_rel(s, rel, &e1, &e2)
747                }))
748            }
749        }
750        impl TypeFolder for BinRelExpander {
751            fn fold_expr(&mut self, expr: &Expr) -> Expr {
752                if let ExprKind::BinaryOp(
753                    rel @ (BinOp::Le(sort) | BinOp::Lt(sort) | BinOp::Ge(sort) | BinOp::Gt(sort)),
754                    e1,
755                    e2,
756                ) = expr.kind()
757                {
758                    self.expand_bin_rel(sort, rel, e1, e2)
759                } else {
760                    expr.super_fold_with(self)
761                }
762            }
763        }
764
765        let mut expander = BinRelExpander {};
766        self.fold_with(&mut expander)
767    }
768
769    /// This is really dumb but we sometimes have the following:
770    ///     Vec { k.0 }
771    /// And we want to really have it rendered as
772    ///     k
773    /// So what we do is look for Tuple or Ctor exprs whose subfields are all
774    /// projections of the same expr, and reduce them if so. Note that we
775    /// **also** require that these are in the same order.
776    pub fn eta_reduce_projs(&self) -> Self {
777        struct EtaReducer;
778        impl TypeFolder for EtaReducer {
779            fn fold_expr(&mut self, expr: &Expr) -> Expr {
780                match expr.kind() {
781                    ExprKind::Tuple(subexprs) => {
782                        let new_subexprs = subexprs
783                            .iter()
784                            .map(|subexpr| subexpr.fold_with(self))
785                            .collect_vec();
786                        if let Some(ExprKind::FieldProj(
787                            e_inner,
788                            FieldProj::Tuple { arity: _, field: 0 },
789                        )) = subexprs.first().map(|e| e.kind())
790                            && new_subexprs[1..].iter().zip(1..).all(|(other_e, i)| {
791                                if let ExprKind::FieldProj(
792                                    other_e_inner,
793                                    FieldProj::Tuple { arity: _, field },
794                                ) = other_e.kind()
795                                {
796                                    field == &i && &e_inner.erase_metadata() == other_e_inner
797                                } else {
798                                    false
799                                }
800                            })
801                        {
802                            e_inner.clone()
803                        } else {
804                            expr.clone()
805                        }
806                    }
807                    ExprKind::Ctor(_ctor, subexprs) => {
808                        let new_subexprs = subexprs
809                            .iter()
810                            .map(|subexpr| subexpr.fold_with(self))
811                            .collect_vec();
812                        if let Some(ExprKind::FieldProj(
813                            e_inner,
814                            FieldProj::Adt { def_id: _, field: 0 },
815                        )) = subexprs.first().map(|e| e.kind())
816                            && new_subexprs[1..].iter().zip(1..).all(|(other_e, i)| {
817                                if let ExprKind::FieldProj(
818                                    other_e_inner,
819                                    FieldProj::Adt { def_id: _, field },
820                                ) = other_e.kind()
821                                {
822                                    field == &i && &e_inner.erase_metadata() == other_e_inner
823                                } else {
824                                    false
825                                }
826                            })
827                        {
828                            e_inner.clone()
829                        } else {
830                            expr.clone()
831                        }
832                    }
833                    _ => expr.super_fold_with(self),
834                }
835            }
836        }
837        self.fold_with(&mut EtaReducer)
838    }
839}
840
841#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, Debug)]
842pub struct ESpan {
843    /// The top-level span information
844    pub span: Span,
845    /// The span for the (base) call-site for def-expanded spans
846    pub base: Option<Span>,
847}
848
849impl ESpan {
850    pub fn new(span: Span) -> Self {
851        Self { span, base: None }
852    }
853
854    pub fn with_base(&self, espan: ESpan) -> Self {
855        Self { span: self.span, base: Some(espan.span) }
856    }
857}
858
859#[derive(
860    Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, Debug, TypeFoldable, TypeVisitable,
861)]
862pub enum BinOp {
863    Iff,
864    Imp,
865    Or,
866    And,
867    Eq,
868    Ne,
869    Gt(Sort),
870    Ge(Sort),
871    Lt(Sort),
872    Le(Sort),
873    Add(Sort),
874    Sub(Sort),
875    Mul(Sort),
876    Div(Sort),
877    Mod(Sort),
878    BitAnd(Sort),
879    BitOr(Sort),
880    BitXor(Sort),
881    BitShl(Sort),
882    BitShr(Sort),
883}
884
885impl BinOp {
886    /// Whether the operation has no interpretation in the logic and must always be encoded as an
887    /// uninterpreted function, i.e., the bitwise operations on `int` (they are interpreted on
888    /// bit vectors).
889    pub fn is_uninterpreted(&self) -> bool {
890        matches!(
891            self,
892            BinOp::BitAnd(Sort::Int)
893                | BinOp::BitOr(Sort::Int)
894                | BinOp::BitXor(Sort::Int)
895                | BinOp::BitShl(Sort::Int)
896                | BinOp::BitShr(Sort::Int)
897        )
898    }
899
900    /// TODO(RJ): bit shady that we're mixing two things here: computing the sort AND determining
901    /// if the operator is uninterpreted...
902    /// The sort `(s, s) -> s` of the uninterpreted function denoting an arithmetic or bitwise
903    /// operation on sort `s`, when it is encoded as such (see [`BinOp::is_uninterpreted`]), or
904    /// `None` for other operations. Ensure this is kept in sync with `prim_op_sort` in `sortck.rs` (on fhir).
905    pub fn uif_sort(&self) -> Option<super::PolyFuncSort> {
906        let sort = match self {
907            BinOp::Add(sort)
908            | BinOp::Sub(sort)
909            | BinOp::Mul(sort)
910            | BinOp::Div(sort)
911            | BinOp::Mod(sort)
912            | BinOp::BitAnd(sort)
913            | BinOp::BitOr(sort)
914            | BinOp::BitXor(sort)
915            | BinOp::BitShl(sort)
916            | BinOp::BitShr(sort) => sort,
917            BinOp::Iff
918            | BinOp::Imp
919            | BinOp::Or
920            | BinOp::And
921            | BinOp::Eq
922            | BinOp::Ne
923            | BinOp::Gt(_)
924            | BinOp::Ge(_)
925            | BinOp::Lt(_)
926            | BinOp::Le(_) => return None,
927        };
928        let fsort = FuncSort::new(vec![sort.clone(), sort.clone()], sort.clone());
929        Some(super::PolyFuncSort::new(List::empty(), fsort))
930    }
931}
932
933#[derive(Clone, Copy, PartialEq, Eq, Hash, Encodable, Debug, Decodable)]
934pub enum UnOp {
935    Not,
936    Neg,
937}
938
939#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, TyEncodable, TyDecodable)]
940pub enum Ctor {
941    /// for indices represented as `struct` in the refinement logic (e.g. using `refined_by` annotations)
942    Struct(DefId),
943    /// for indices represented as  `enum` in the refinement logic (e.g. using `reflected` annotations)
944    Enum(DefId, VariantIdx),
945    /// for the builtin indices of raw pointers
946    RawPtr,
947}
948
949impl Ctor {
950    fn is_enum(&self) -> bool {
951        matches!(self, Self::Enum(..))
952    }
953
954    pub fn def_id_and_variant(&self) -> Option<(DefId, VariantIdx)> {
955        match self {
956            Self::Struct(def_id) => Some((*def_id, FIRST_VARIANT)),
957            Self::Enum(def_id, variant_idx) => Some((*def_id, *variant_idx)),
958            Self::RawPtr => None,
959        }
960    }
961}
962
963/// # Primitive Properties
964/// Given a primop `op` with signature `(t1,...,tn) -> t`
965/// We define a refined type for `op` expressed as a `RuleMatcher`
966///
967/// ```text
968/// op :: (x1: t1, ..., xn: tn) -> { t[op_val[op](x1,...,xn)] | op_rel[x1,...,xn] }
969/// ```
970/// That is, using two *uninterpreted functions* `op_val` and `op_rel` that respectively denote
971/// 1. The _value_ of the primop, and
972/// 2. Some invariant _relation_ that holds for the primop.
973///
974/// The latter can be extended by the user via a `property` definition, which allows us
975/// to customize primops like `<<` with extra "facts" or lemmas. See `tests/tests/pos/surface/primops00.rs` for an example.
976#[derive(Debug, Clone, TyEncodable, TyDecodable, PartialEq, Eq, Hash)]
977pub enum InternalFuncKind {
978    /// UIF representing the relationship of a primop
979    Rel(BinOp),
980    // Conversions betweeen Sorts
981    Cast,
982}
983
984#[derive(Debug, Clone, TyEncodable, TyDecodable, PartialEq, Eq, Hash)]
985pub enum SpecFuncKind {
986    /// Theory symbols *interpreted* by the SMT solver
987    Thy(liquid_fixpoint::ThyFunc),
988    /// User-defined function. This can be either a function with a body or a UIF.
989    Def(FluxDefId),
990}
991
992#[derive(
993    Clone, PartialEq, Eq, Hash, TyEncodable, Debug, TyDecodable, TypeVisitable, TypeFoldable,
994)]
995pub enum QuantDom {
996    Bounded { start: usize, end: usize },
997    Unbounded,
998}
999
1000#[derive(Clone, PartialEq, Eq, Hash, TyEncodable, Debug, TyDecodable)]
1001pub enum ExprKind {
1002    Var(Var),
1003    Local(Local),
1004    Constant(Constant),
1005    /// A rust constant. This can be either `DefKind::Const` or `DefKind::AssocConst`
1006    ConstDefId(DefId),
1007    BinaryOp(BinOp, Expr, Expr),
1008    GlobalFunc(SpecFuncKind),
1009    InternalFunc(InternalFuncKind),
1010    UnaryOp(UnOp, Expr),
1011    FieldProj(Expr, FieldProj),
1012    /// A variant used in the logic to represent a variant of an ADT as a pair of the `DefId` and variant-index
1013    Ctor(Ctor, List<Expr>),
1014    Tuple(List<Expr>),
1015    PathProj(Expr, FieldIdx),
1016    IfThenElse(Expr, Expr, Expr),
1017    KVar(KVar),
1018    WKVar(WKVar),
1019    Alias(AliasReft, List<Expr>),
1020    Let(Expr, Binder<Expr>),
1021    /// Function application. The syntax allows arbitrary expressions in function position, but in
1022    /// practice we are restricted by what's possible to encode in fixpoint. In a nutshell, we need
1023    /// to make sure that expressions that can't be encoded are eliminated before we generate the
1024    /// fixpoint constraint. Most notably, lambda abstractions have to be fully applied before
1025    /// encoding into fixpoint (except when they appear as an index at the top-level).
1026    App(Expr, List<SortArg>, List<Expr>),
1027    /// Lambda abstractions. They are purely syntactic and we don't encode them in the logic. As such,
1028    /// they have some syntactic restrictions that we must carefully maintain:
1029    ///
1030    /// 1. They can appear as an index at the top level.
1031    /// 2. We can only substitute an abstraction for a variable in function position (or as an index).
1032    ///    More generally, we need to partially evaluate expressions such that all abstractions in
1033    ///    non-index position are eliminated before encoding into fixpoint. Right now, the
1034    ///    implementation only evaluates abstractions that are immediately applied to arguments,
1035    ///    thus the restriction.
1036    Abs(Lambda),
1037
1038    /// Bounded quantifiers `exists i in 0..4 { pred(i) }` and `forall i in 0..4 { pred(i) }`.
1039    Quant(fhir::QuantKind, QuantDom, Binder<Expr>),
1040    /// A hole is an expression that must be inferred either *semantically* by generating a kvar or
1041    /// *syntactically* by generating an evar. Whether a hole can be inferred semantically or
1042    /// syntactically depends on the position it appears: only holes appearing in predicate position
1043    /// can be inferred with a kvar (provided it satisfies the fixpoint horn constraints) and only
1044    /// holes used as an index (a position that fully determines their value) can be inferred with
1045    /// an evar.
1046    ///
1047    /// Holes are implicitly defined in a scope, i.e., their solution could mention free and bound
1048    /// variables in this scope. This must be considered when generating an inference variables for
1049    /// them (either evar or kvar). In fact, the main reason we have holes is that we want to
1050    /// decouple the places where we generate holes (where we don't want to worry about the scope),
1051    /// and the places where we generate inference variable for them (where we do need to worry
1052    /// about the scope).
1053    Hole(HoleKind),
1054    /// Is the expression constructed from constructor of the given DefId (which should be `reflected` Enum)
1055    IsCtor(DefId, VariantIdx, Expr),
1056}
1057
1058impl ExprKind {
1059    fn intern(self) -> Expr {
1060        Expr { kind: Interned::new(self), espan: None }
1061    }
1062}
1063
1064#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, Debug)]
1065pub enum AggregateKind {
1066    Tuple(usize),
1067    Adt(DefId),
1068}
1069
1070#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, Debug)]
1071pub enum FieldProj {
1072    Tuple { arity: usize, field: u32 },
1073    Adt { def_id: DefId, field: u32 },
1074    RawPtr { field: RawPtrField },
1075}
1076
1077#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, Debug)]
1078pub enum RawPtrField {
1079    Base,
1080    Addr,
1081    Size,
1082}
1083
1084struct RawPtrFieldData {
1085    field: RawPtrField,
1086    index: u32,
1087    name: &'static str,
1088    sort: Sort,
1089}
1090
1091impl FieldProj {
1092    pub fn arity(&self, genv: GlobalEnv) -> QueryResult<usize> {
1093        match self {
1094            FieldProj::Tuple { arity, .. } => Ok(*arity),
1095            FieldProj::Adt { def_id, .. } => {
1096                Ok(genv.adt_sort_def_of(*def_id)?.struct_variant().fields())
1097            }
1098            FieldProj::RawPtr { .. } => Ok(RawPtrField::arity()),
1099        }
1100    }
1101
1102    pub fn field_idx(&self) -> u32 {
1103        match self {
1104            FieldProj::Tuple { field, .. } | FieldProj::Adt { field, .. } => *field,
1105            FieldProj::RawPtr { field } => field.index(),
1106        }
1107    }
1108}
1109
1110impl RawPtrField {
1111    const DATA: [RawPtrFieldData; 3] = [
1112        RawPtrFieldData { field: Self::Base, index: 0, name: "base", sort: Sort::Int },
1113        RawPtrFieldData { field: Self::Addr, index: 1, name: "addr", sort: Sort::Int },
1114        RawPtrFieldData { field: Self::Size, index: 2, name: "size", sort: Sort::Int },
1115    ];
1116
1117    pub fn iter() -> impl ExactSizeIterator<Item = Self> {
1118        Self::DATA.iter().map(|data| data.field)
1119    }
1120
1121    pub fn arity() -> usize {
1122        Self::DATA.len()
1123    }
1124
1125    fn data(self) -> &'static RawPtrFieldData {
1126        Self::DATA.iter().find(|data| data.field == self).unwrap()
1127    }
1128
1129    pub fn from_name(name: Symbol) -> Option<Self> {
1130        Self::DATA
1131            .iter()
1132            .find(|data| data.name == name.as_str())
1133            .map(|data| data.field)
1134    }
1135
1136    pub fn from_index(index: u32) -> Option<Self> {
1137        Self::DATA
1138            .iter()
1139            .find(|data| data.index == index)
1140            .map(|data| data.field)
1141    }
1142
1143    pub fn sort(self) -> Sort {
1144        self.data().sort.clone()
1145    }
1146
1147    pub fn index(self) -> u32 {
1148        self.data().index
1149    }
1150
1151    pub fn name(self) -> &'static str {
1152        self.data().name
1153    }
1154
1155    pub fn symbol(self) -> Symbol {
1156        match self {
1157            Self::Base => sym::base,
1158            Self::Addr => sym::addr,
1159            Self::Size => rustc_span::sym::size,
1160        }
1161    }
1162}
1163
1164/// The position where a [hole] appears. This determines how it will be inferred. This is related
1165/// to, but not the same as, an [`InferMode`].
1166///
1167/// [`InferMode`]: super::InferMode
1168/// [hole]: ExprKind::Hole
1169#[derive(
1170    Debug, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeFoldable, TypeVisitable,
1171)]
1172pub enum HoleKind {
1173    /// A hole in predicate position (e.g., the predicate in a [`TyKind::Constr`]). It will be
1174    /// inferred by generating a kvar.
1175    ///
1176    /// [`TyKind::Constr`]: super::TyKind::Constr
1177    Pred,
1178    /// A hole used as a refinement argument or index. It will be inferred by generating an evar.
1179    /// The expression filling the hole must have the provided sort.
1180    ///
1181    /// NOTE(nilehmann) we used to require the `Sort` for generating the evar because we needed it
1182    /// to eta-expand aggregate sorts. We've since removed this behavior but I'm keeping it here
1183    /// just in case. We could remove in case it becomes too problematic.
1184    Expr(Sort),
1185}
1186
1187/// In theory a kvar is just an unknown predicate that can use some variables in scope. In practice,
1188/// fixpoint distinguishes a list of leading arguments, the kvar's *self arguments*, from the rest,
1189/// which we call the *scope*. Fixpoint only instantiates qualifiers whose first parameter is a self
1190/// argument, so a kvar without self arguments can only be solved to `true` or `false`. The number
1191/// of self arguments is passed to fixpoint with the `:self` annotation.
1192#[derive(Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)]
1193pub struct KVar {
1194    pub kvid: KVid,
1195    /// The number of arguments consider to be *self arguments*.
1196    pub self_args: usize,
1197    /// The list of *all* arguments with the self arguments at the beginning, i.e., the
1198    /// list of self arguments followed by the scope.
1199    pub args: List<Expr>,
1200}
1201
1202#[derive(
1203    Debug, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable,
1204)]
1205pub struct WKVid {
1206    /// Weak KVars right now are always associated with a function definition.
1207    /// Weak KVars can in theory be put wherever we can validly add a refinement
1208    pub parent_fn: DefId,
1209    /// There's no reason why this has to be KVid, and in principle it may be
1210    /// better served as just a number or a new index unto itself.
1211    pub id: KVid,
1212}
1213
1214impl WKVid {
1215    pub fn new(parent_fn: DefId, id: KVid) -> Self {
1216        Self { parent_fn, id }
1217    }
1218}
1219
1220/// A weak kvar is like a kvar with the exception that it infers the weakest
1221/// condition necessary instead of the strongest condition. Due to the way we
1222/// generate these kvars (on fn_sigs, rather than during constraint generation),
1223/// they also in theory are global.
1224#[derive(Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)]
1225pub struct WKVar {
1226    pub wkvid: WKVid,
1227    /// Analagous to KVar self arguments except we require that instantiations
1228    /// use *at least one* of the self_args (unless there are none, in which
1229    /// case there is no such requirement).
1230    ///
1231    /// This is mostly relevant for weak kvars corresponding to function
1232    /// *outputs*. Consider the signature
1233    ///
1234    ///     foo: fn(x: usize) -> bool{b: $wk1(b, x)}
1235    ///            requires $wk0(x)
1236    ///
1237    /// In this case self_args would be 1 (corresponding to the bool `b`).
1238    /// Consider now the snippet
1239    ///
1240    ///     let b = foo(x);
1241    ///     assert(x > 0);
1242    ///
1243    /// Suppose the assertion fails. Without the self_args requirement, we
1244    /// could validly instantiate
1245    ///
1246    ///     $wk1(b, x) := x > 0
1247    ///
1248    /// But this is somewhat nonsensical because only in exceptional cases
1249    /// does it make sense for `foo` to somehow "add" information to its
1250    /// **argument** (`x`).
1251    ///
1252    /// By checking for the presence of **at least one** self arg, we
1253    /// ensure that the self argument is "used"; fixpoint does a similar check.
1254    ///
1255    /// This is a syntactic underapproximation and doesn't preclude things like
1256    ///
1257    ///     $wk1(b, x) := (b => true) && (x > 0)
1258    ///
1259    /// But we could conceive of a more sophisticated check using the self_args.
1260    pub self_args: usize,
1261    /// All arguments with self arguments at the beginning.
1262    pub args: List<Expr>,
1263}
1264
1265#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Encodable, Decodable)]
1266pub struct EarlyReftParam {
1267    pub index: u32,
1268    pub name: Symbol,
1269}
1270
1271#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Encodable, Decodable, Debug)]
1272pub struct BoundReft {
1273    pub var: BoundVar,
1274    pub kind: BoundReftKind,
1275}
1276
1277#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TyEncodable, TyDecodable)]
1278pub enum Var {
1279    Free(Name),
1280    Bound(DebruijnIndex, BoundReft),
1281    EarlyParam(EarlyReftParam),
1282    EVar(EVid),
1283    ConstGeneric(ParamConst),
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, TyEncodable, TyDecodable)]
1287pub struct Path {
1288    pub loc: Loc,
1289    projection: List<FieldIdx>,
1290}
1291
1292#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, TyEncodable, TyDecodable)]
1293pub enum Loc {
1294    Local(Local),
1295    Var(Var),
1296}
1297
1298newtype_index! {
1299    /// *E*xistential *v*ariable *id*
1300    #[debug_format = "?{}e"]
1301    #[orderable]
1302    #[encodable]
1303    pub struct EVid {}
1304}
1305
1306newtype_index! {
1307    #[debug_format = "$k{}"]
1308    #[encodable]
1309    pub struct KVid {}
1310}
1311
1312impl liquid_fixpoint::Identifier for KVid {
1313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1314        write!(f, "k{}", self.as_u32())
1315    }
1316}
1317
1318newtype_index! {
1319    #[debug_format = "a{}"]
1320    #[orderable]
1321    #[encodable]
1322    pub struct Name {}
1323}
1324
1325#[derive(Copy, Debug, Clone)]
1326pub enum NameProvenance {
1327    Unknown,
1328    UnfoldBoundReft(BoundReftKind),
1329}
1330
1331impl NameProvenance {
1332    pub fn opt_symbol(&self) -> Option<Symbol> {
1333        match &self {
1334            NameProvenance::UnfoldBoundReft(BoundReftKind::Named(name)) => Some(*name),
1335            _ => None,
1336        }
1337    }
1338}
1339
1340#[derive(PartialEq, Eq, Hash, Debug)]
1341pub enum PrettyVar<V> {
1342    Local(V),
1343    Param(EarlyReftParam),
1344}
1345
1346impl<V: Copy + Into<usize>> PrettyVar<V> {
1347    pub fn as_subscript(&self) -> String {
1348        let idx = match self {
1349            PrettyVar::Local(v) => (*v).into(),
1350            PrettyVar::Param(p) => p.index as usize,
1351        };
1352        as_subscript(idx)
1353    }
1354}
1355
1356pub struct PrettyMap<V: Eq + Hash> {
1357    map: UnordMap<PrettyVar<V>, String>,
1358    count: UnordMap<Symbol, usize>,
1359}
1360
1361impl<V: Eq + Hash + Copy + Into<usize>> PrettyMap<V> {
1362    pub fn new() -> Self {
1363        PrettyMap { map: UnordMap::default(), count: UnordMap::default() }
1364    }
1365
1366    pub fn set(&mut self, var: PrettyVar<V>, prefix: Option<Symbol>) -> String {
1367        // if already defined, return it
1368        if let Some(symbol) = self.map.get(&var) {
1369            return symbol.clone();
1370        }
1371        // else define it, and stash
1372        let symbol = if let Some(prefix) = prefix {
1373            let index = self.count.entry(prefix).or_insert(0);
1374            let symbol = format!("{}{}", prefix, as_subscript(*index));
1375            *index += 1;
1376            symbol
1377        } else {
1378            format!("a'{}", var.as_subscript())
1379        };
1380        self.map.insert(var, symbol.clone());
1381        symbol
1382    }
1383
1384    pub fn get(&self, key: &PrettyVar<V>) -> String {
1385        match self.map.get(key) {
1386            Some(s) => s.clone(),
1387            None => format!("a'{}", key.as_subscript()),
1388        }
1389    }
1390}
1391
1392impl KVar {
1393    pub fn new(kvid: KVid, self_args: usize, args: Vec<Expr>) -> Self {
1394        KVar { kvid, self_args, args: List::from_vec(args) }
1395    }
1396
1397    fn self_args(&self) -> &[Expr] {
1398        &self.args[..self.self_args]
1399    }
1400
1401    fn scope(&self) -> &[Expr] {
1402        &self.args[self.self_args..]
1403    }
1404}
1405
1406impl WKVar {
1407    fn self_args(&self) -> &[Expr] {
1408        &self.args[..self.self_args]
1409    }
1410
1411    fn scope(&self) -> &[Expr] {
1412        &self.args[self.self_args..]
1413    }
1414}
1415
1416impl Var {
1417    pub fn to_expr(&self) -> Expr {
1418        Expr::var(*self)
1419    }
1420
1421    pub fn shift_in(&self, amount: u32) -> Self {
1422        match self {
1423            Var::Bound(idx, breft) => Var::Bound(idx.shifted_in(amount), *breft),
1424            _ => *self,
1425        }
1426    }
1427
1428    pub fn shift_out(&self, amount: u32) -> Self {
1429        match self {
1430            Var::Bound(idx, breft) => Var::Bound(idx.shifted_out(amount), *breft),
1431            _ => *self,
1432        }
1433    }
1434}
1435
1436impl Path {
1437    pub fn new(loc: Loc, projection: impl Into<List<FieldIdx>>) -> Path {
1438        Path { loc, projection: projection.into() }
1439    }
1440
1441    pub fn projection(&self) -> &[FieldIdx] {
1442        &self.projection[..]
1443    }
1444
1445    pub fn to_expr(&self) -> Expr {
1446        self.projection
1447            .iter()
1448            .fold(self.loc.to_expr(), |e, f| Expr::path_proj(e, *f))
1449    }
1450
1451    pub fn to_loc(&self) -> Option<Loc> {
1452        if self.projection.is_empty() { Some(self.loc) } else { None }
1453    }
1454}
1455
1456impl Loc {
1457    pub fn to_expr(&self) -> Expr {
1458        match self {
1459            Loc::Local(local) => Expr::local(*local),
1460            Loc::Var(var) => Expr::var(*var),
1461        }
1462    }
1463}
1464
1465macro_rules! impl_ops {
1466    ($($op:ident: $method:ident),*) => {$(
1467        impl<Rhs> std::ops::$op<Rhs> for Expr
1468        where
1469            Rhs: Into<Expr>,
1470        {
1471            type Output = Expr;
1472
1473            fn $method(self, rhs: Rhs) -> Self::Output {
1474                let sort = crate::rty::Sort::Int;
1475                Expr::binary_op(BinOp::$op(sort), self, rhs)
1476            }
1477        }
1478
1479        impl<Rhs> std::ops::$op<Rhs> for &Expr
1480        where
1481            Rhs: Into<Expr>,
1482        {
1483            type Output = Expr;
1484
1485            fn $method(self, rhs: Rhs) -> Self::Output {
1486                let sort = crate::rty::Sort::Int;
1487                Expr::binary_op(BinOp::$op(sort), self, rhs)
1488            }
1489        }
1490    )*};
1491}
1492impl_ops!(Add: add, Sub: sub, Mul: mul, Div: div);
1493
1494impl From<i32> for Expr {
1495    fn from(value: i32) -> Self {
1496        Expr::constant(Constant::from(value))
1497    }
1498}
1499
1500impl From<&Expr> for Expr {
1501    fn from(e: &Expr) -> Self {
1502        e.clone()
1503    }
1504}
1505
1506impl From<Path> for Expr {
1507    fn from(path: Path) -> Self {
1508        path.to_expr()
1509    }
1510}
1511
1512impl From<Name> for Expr {
1513    fn from(name: Name) -> Self {
1514        Expr::fvar(name)
1515    }
1516}
1517
1518impl From<Var> for Expr {
1519    fn from(var: Var) -> Self {
1520        Expr::var(var)
1521    }
1522}
1523
1524impl From<SpecFuncKind> for Expr {
1525    fn from(kind: SpecFuncKind) -> Self {
1526        Expr::global_func(kind)
1527    }
1528}
1529
1530impl From<InternalFuncKind> for Expr {
1531    fn from(kind: InternalFuncKind) -> Self {
1532        Expr::internal_func(kind)
1533    }
1534}
1535
1536impl From<Loc> for Path {
1537    fn from(loc: Loc) -> Self {
1538        Path::new(loc, vec![])
1539    }
1540}
1541
1542impl From<Name> for Loc {
1543    fn from(name: Name) -> Self {
1544        Loc::Var(Var::Free(name))
1545    }
1546}
1547
1548impl From<Local> for Loc {
1549    fn from(local: Local) -> Self {
1550        Loc::Local(local)
1551    }
1552}
1553
1554#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encodable, Decodable)]
1555pub struct Real(pub Symbol);
1556
1557#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encodable, Decodable)]
1558pub enum Constant {
1559    Int(BigInt),
1560    Real(Real),
1561    Bool(bool),
1562    Str(Symbol),
1563    Char(char),
1564    BitVec(u128, u32),
1565}
1566
1567impl Constant {
1568    pub const ZERO: Constant = Constant::Int(BigInt::ZERO);
1569    pub const ONE: Constant = Constant::Int(BigInt::ONE);
1570    pub const TRUE: Constant = Constant::Bool(true);
1571
1572    fn to_bool(self) -> Option<bool> {
1573        match self {
1574            Constant::Bool(b) => Some(b),
1575            _ => None,
1576        }
1577    }
1578
1579    fn to_int(self) -> Option<BigInt> {
1580        match self {
1581            Constant::Int(n) => Some(n),
1582            _ => None,
1583        }
1584    }
1585
1586    pub fn iff(&self, other: &Constant) -> Option<Constant> {
1587        let b1 = self.to_bool()?;
1588        let b2 = other.to_bool()?;
1589        Some(Constant::Bool(b1 == b2))
1590    }
1591
1592    pub fn imp(&self, other: &Constant) -> Option<Constant> {
1593        let b1 = self.to_bool()?;
1594        let b2 = other.to_bool()?;
1595        Some(Constant::Bool(!b1 || b2))
1596    }
1597
1598    pub fn or(&self, other: &Constant) -> Option<Constant> {
1599        let b1 = self.to_bool()?;
1600        let b2 = other.to_bool()?;
1601        Some(Constant::Bool(b1 || b2))
1602    }
1603
1604    pub fn and(&self, other: &Constant) -> Option<Constant> {
1605        let b1 = self.to_bool()?;
1606        let b2 = other.to_bool()?;
1607        Some(Constant::Bool(b1 && b2))
1608    }
1609
1610    pub fn eq(&self, other: &Constant) -> Constant {
1611        Constant::Bool(*self == *other)
1612    }
1613
1614    pub fn ne(&self, other: &Constant) -> Constant {
1615        Constant::Bool(*self != *other)
1616    }
1617
1618    pub fn gt(&self, other: &Constant) -> Option<Constant> {
1619        let n1 = self.to_int()?;
1620        let n2 = other.to_int()?;
1621        Some(Constant::Bool(n1 > n2))
1622    }
1623
1624    pub fn ge(&self, other: &Constant) -> Option<Constant> {
1625        let n1 = self.to_int()?;
1626        let n2 = other.to_int()?;
1627        Some(Constant::Bool(n1 >= n2))
1628    }
1629
1630    pub fn from_scalar_int<'tcx, T>(tcx: TyCtxt<'tcx>, scalar: ScalarInt, t: &T) -> Option<Self>
1631    where
1632        T: ToRustc<'tcx, T = rustc_middle::ty::Ty<'tcx>>,
1633    {
1634        use rustc_middle::ty::TyKind;
1635        let ty = t.to_rustc(tcx);
1636        match ty.kind() {
1637            TyKind::Int(int_ty) => Some(Constant::from(scalar_to_int(tcx, scalar, *int_ty))),
1638            TyKind::Uint(uint_ty) => Some(Constant::from(scalar_to_uint(tcx, scalar, *uint_ty))),
1639            TyKind::Bool => {
1640                let b = scalar_to_bits(tcx, scalar, ty);
1641                Some(Constant::Bool(b != 0))
1642            }
1643            TyKind::Char => {
1644                let b = scalar_to_bits(tcx, scalar, ty);
1645                Some(Constant::Char(char::from_u32(b as u32)?))
1646            }
1647            _ => bug!(),
1648        }
1649    }
1650
1651    /// See [`BigInt::int_min`]
1652    pub fn int_min(bit_width: u32) -> Constant {
1653        Constant::Int(BigInt::int_min(bit_width))
1654    }
1655
1656    /// See [`BigInt::int_max`]
1657    pub fn int_max(bit_width: u32) -> Constant {
1658        Constant::Int(BigInt::int_max(bit_width))
1659    }
1660
1661    /// See [`BigInt::uint_max`]
1662    pub fn uint_max(bit_width: u32) -> Constant {
1663        Constant::Int(BigInt::uint_max(bit_width))
1664    }
1665}
1666
1667impl From<i32> for Constant {
1668    fn from(c: i32) -> Self {
1669        Constant::Int(c.into())
1670    }
1671}
1672
1673impl From<usize> for Constant {
1674    fn from(u: usize) -> Self {
1675        Constant::Int(u.into())
1676    }
1677}
1678
1679impl From<u32> for Constant {
1680    fn from(c: u32) -> Self {
1681        Constant::Int(c.into())
1682    }
1683}
1684
1685impl From<u64> for Constant {
1686    fn from(c: u64) -> Self {
1687        Constant::Int(c.into())
1688    }
1689}
1690
1691impl From<u128> for Constant {
1692    fn from(c: u128) -> Self {
1693        Constant::Int(c.into())
1694    }
1695}
1696
1697impl From<i128> for Constant {
1698    fn from(c: i128) -> Self {
1699        Constant::Int(c.into())
1700    }
1701}
1702
1703impl From<bool> for Constant {
1704    fn from(b: bool) -> Self {
1705        Constant::Bool(b)
1706    }
1707}
1708
1709impl From<Symbol> for Constant {
1710    fn from(s: Symbol) -> Self {
1711        Constant::Str(s)
1712    }
1713}
1714
1715impl From<char> for Constant {
1716    fn from(c: char) -> Self {
1717        Constant::Char(c)
1718    }
1719}
1720
1721impl_internable!(ExprKind);
1722impl_slice_internable!(Expr, KVar);
1723
1724#[derive(Debug)]
1725pub struct FieldBind<T> {
1726    pub name: Symbol,
1727    pub value: T,
1728}
1729
1730impl<T: Pretty> Pretty for FieldBind<T> {
1731    fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1732        w!(cx, f, "{}: {:?}", ^self.name, &self.value)
1733    }
1734}
1735
1736pub(crate) mod pretty {
1737
1738    use flux_rustc_bridge::def_id_to_string;
1739
1740    use super::*;
1741    use crate::name_of_thy_func;
1742
1743    #[derive(PartialEq, Eq, PartialOrd, Ord)]
1744    enum Precedence {
1745        Iff,
1746        Imp,
1747        Or,
1748        And,
1749        Cmp,
1750        Bitvec,
1751        AddSub,
1752        MulDiv,
1753    }
1754
1755    impl BinOp {
1756        fn precedence(&self) -> Precedence {
1757            match self {
1758                BinOp::Iff => Precedence::Iff,
1759                BinOp::Imp => Precedence::Imp,
1760                BinOp::Or => Precedence::Or,
1761                BinOp::And => Precedence::And,
1762                BinOp::Eq
1763                | BinOp::Ne
1764                | BinOp::Gt(_)
1765                | BinOp::Lt(_)
1766                | BinOp::Ge(_)
1767                | BinOp::Le(_) => Precedence::Cmp,
1768                BinOp::Add(_) | BinOp::Sub(_) => Precedence::AddSub,
1769                BinOp::Mul(_) | BinOp::Div(_) | BinOp::Mod(_) => Precedence::MulDiv,
1770                BinOp::BitAnd(_)
1771                | BinOp::BitOr(_)
1772                | BinOp::BitShl(_)
1773                | BinOp::BitShr(_)
1774                | BinOp::BitXor(_) => Precedence::Bitvec,
1775            }
1776        }
1777    }
1778
1779    impl Precedence {
1780        pub fn is_associative(&self) -> bool {
1781            !matches!(self, Precedence::Imp | Precedence::Cmp)
1782        }
1783    }
1784
1785    pub fn should_parenthesize(op: &BinOp, child: &Expr) -> bool {
1786        if let ExprKind::BinaryOp(child_op, ..) = child.kind() {
1787            child_op.precedence() < op.precedence()
1788                || (child_op.precedence() == op.precedence() && !op.precedence().is_associative())
1789        } else {
1790            false
1791        }
1792    }
1793
1794    impl Pretty for Ctor {
1795        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1796            match self {
1797                Ctor::Struct(def_id) => {
1798                    w!(cx, f, "{:?}", def_id)
1799                }
1800                Ctor::Enum(def_id, variant_idx) => {
1801                    w!(cx, f, "{:?}::{:?}", def_id, ^variant_idx)
1802                }
1803                Ctor::RawPtr => w!(cx, f, "ptr"),
1804            }
1805        }
1806    }
1807
1808    impl Pretty for fhir::QuantKind {
1809        fn fmt(&self, _cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1810            match self {
1811                fhir::QuantKind::Exists => w!(cx, f, "∃"),
1812                fhir::QuantKind::Forall => w!(cx, f, "∀"),
1813            }
1814        }
1815    }
1816
1817    impl Pretty for InternalFuncKind {
1818        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1819            match self {
1820                InternalFuncKind::Rel(op) => w!(cx, f, "[{:?}]?", op),
1821                InternalFuncKind::Cast => w!(cx, f, "cast"),
1822            }
1823        }
1824    }
1825
1826    impl Pretty for QuantDom {
1827        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1828            match self {
1829                QuantDom::Bounded { start, end } => w!(cx, f, "in {} .. {}", ^start, ^end),
1830                QuantDom::Unbounded => Ok(()),
1831            }
1832        }
1833    }
1834
1835    impl Pretty for Expr {
1836        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1837            let e = if cx.simplify_exprs {
1838                self.simplify(&SnapshotMap::default())
1839            } else {
1840                self.clone()
1841            };
1842            match e.kind() {
1843                ExprKind::Var(var) => w!(cx, f, "{:?}", var),
1844                ExprKind::Local(local) => w!(cx, f, "{:?}", ^local),
1845                ExprKind::ConstDefId(did) => w!(cx, f, "{}", ^def_id_to_string(*did)),
1846                ExprKind::Constant(c) => w!(cx, f, "{:?}", c),
1847
1848                ExprKind::BinaryOp(op, e1, e2) => {
1849                    if should_parenthesize(op, e1) {
1850                        w!(cx, f, "({:?})", e1)?;
1851                    } else {
1852                        w!(cx, f, "{:?}", e1)?;
1853                    }
1854                    if matches!(op, BinOp::Div(_)) {
1855                        w!(cx, f, "{:?}", op)?;
1856                    } else {
1857                        w!(cx, f, " {:?} ", op)?;
1858                    }
1859                    if should_parenthesize(op, e2) {
1860                        w!(cx, f, "({:?})", e2)?;
1861                    } else {
1862                        w!(cx, f, "{:?}", e2)?;
1863                    }
1864                    Ok(())
1865                }
1866                ExprKind::UnaryOp(op, e) => {
1867                    if e.is_atom() {
1868                        w!(cx, f, "{:?}{:?}", op, e)
1869                    } else {
1870                        w!(cx, f, "{:?}({:?})", op, e)
1871                    }
1872                }
1873                ExprKind::FieldProj(e, proj) if let ExprKind::Ctor(_, _) = e.kind() => {
1874                    // special case to avoid printing `{n:12}.n` as `12.n` but instead, just print `12`
1875                    // TODO: maintain an invariant that `FieldProj` never has a Ctor as first argument (as always reduced)
1876                    w!(cx, f, "{:?}", e.proj_and_reduce(*proj))
1877                }
1878                ExprKind::FieldProj(e, proj) => {
1879                    if e.is_atom() {
1880                        w!(cx, f, "{:?}.{}", e, ^fmt_field_proj(cx, *proj))
1881                    } else {
1882                        w!(cx, f, "({:?}).{}", e, ^fmt_field_proj(cx, *proj))
1883                    }
1884                }
1885                ExprKind::Tuple(flds) => {
1886                    if let [e] = &flds[..] {
1887                        w!(cx, f, "({:?},)", e)
1888                    } else {
1889                        w!(cx, f, "({:?})", join!(", ", flds))
1890                    }
1891                }
1892                ExprKind::IsCtor(def_id, variant_idx, idx) => {
1893                    w!(cx, f, "({:?} is {:?}::{:?})", idx, def_id, ^variant_idx)
1894                }
1895                ExprKind::Ctor(ctor, flds) => {
1896                    if matches!(ctor, Ctor::RawPtr) {
1897                        let fields = iter::zip(RawPtrField::iter(), flds)
1898                            .map(|(field, value)| {
1899                                FieldBind { name: field.symbol(), value: value.clone() }
1900                            })
1901                            .collect_vec();
1902                        return w!(cx, f, "ptr {{ {:?} }}", join!(", ", fields));
1903                    }
1904                    let Some((def_id, variant_idx)) = ctor.def_id_and_variant() else {
1905                        unreachable!()
1906                    };
1907                    if let Some(adt_sort_def) = cx.adt_sort_def_of(def_id) {
1908                        let variant = adt_sort_def.variant(variant_idx).field_names();
1909                        let fields = iter::zip(variant, flds)
1910                            .map(|(name, value)| FieldBind { name: *name, value: value.clone() })
1911                            .collect_vec();
1912                        match ctor {
1913                            Ctor::Struct(_) => {
1914                                w!(cx, f, "{:?} {{ {:?} }}", def_id, join!(", ", fields))
1915                            }
1916                            Ctor::Enum(_, idx) => {
1917                                if fields.is_empty() {
1918                                    w!(cx, f, "{:?}::{:?}", def_id, ^idx.index())
1919                                } else {
1920                                    w!(cx, f, "{:?}::{:?}({:?})", def_id, ^idx.index(), join!(", ", fields))
1921                                }
1922                            }
1923                            Ctor::RawPtr => unreachable!(),
1924                        }
1925                    } else {
1926                        match ctor {
1927                            Ctor::Struct(_) => {
1928                                w!(cx, f, "{:?} {{ {:?} }}", def_id, join!(", ", flds))
1929                            }
1930                            Ctor::Enum(_, idx) => {
1931                                w!(cx, f, "{:?}::{:?} {{ {:?} }}", def_id, ^idx, join!(", ", flds))
1932                            }
1933                            Ctor::RawPtr => unreachable!(),
1934                        }
1935                    }
1936                }
1937                ExprKind::PathProj(e, field) => {
1938                    if e.is_atom() {
1939                        w!(cx, f, "{:?}.{:?}", e, field)
1940                    } else {
1941                        w!(cx, f, "({:?}).{:?}", e, field)
1942                    }
1943                }
1944                ExprKind::App(func, _, args) => {
1945                    w!(cx, f, "{:?}({})",
1946                        parens!(func, !func.is_atom()),
1947                        ^args
1948                            .iter()
1949                            .format_with(", ", |arg, f| f(&format_args_cx!(cx, "{:?}", arg)))
1950                    )
1951                }
1952                ExprKind::IfThenElse(p, e1, e2) => {
1953                    w!(cx, f, "if {:?} {{ {:?} }} else {{ {:?} }}", p, e1, e2)
1954                }
1955                ExprKind::Hole(_) => {
1956                    w!(cx, f, "*")
1957                }
1958                ExprKind::KVar(kvar) => {
1959                    w!(cx, f, "{:?}", kvar)
1960                }
1961                ExprKind::WKVar(wkvar) => {
1962                    w!(cx, f, "{:?}", wkvar)
1963                }
1964                ExprKind::Alias(alias, args) => {
1965                    w!(cx, f, "{:?}({:?})", alias, join!(", ", args))
1966                }
1967                ExprKind::Abs(lam) => {
1968                    w!(cx, f, "{:?}", lam)
1969                }
1970                ExprKind::GlobalFunc(SpecFuncKind::Def(did)) => {
1971                    w!(cx, f, "{}", ^did.name())
1972                }
1973                ExprKind::GlobalFunc(SpecFuncKind::Thy(itf)) => {
1974                    if let Some(name) = name_of_thy_func(*itf) {
1975                        w!(cx, f, "{}", ^name)
1976                    } else {
1977                        w!(cx, f, "<error>")
1978                    }
1979                }
1980                ExprKind::InternalFunc(func) => {
1981                    w!(cx, f, "{:?}", func)
1982                }
1983                ExprKind::Quant(kind, dom, body) => {
1984                    let vars = body.vars();
1985                    cx.with_bound_vars(vars, || {
1986                        w!(cx, f, "{:?} {:?} {{ {:?} }}", kind, dom, body.skip_binder_ref())
1987                    })
1988                }
1989                ExprKind::Let(init, body) => {
1990                    let vars = body.vars();
1991                    cx.with_bound_vars(vars, || {
1992                        cx.fmt_bound_vars(false, "(let ", vars, " = ", f)?;
1993                        w!(cx, f, "{:?} in {:?})", init, body.skip_binder_ref())
1994                    })
1995                }
1996            }
1997        }
1998    }
1999
2000    fn fmt_field_proj(cx: &PrettyCx, proj: FieldProj) -> String {
2001        if let FieldProj::Adt { def_id, field } = proj
2002            && let Some(adt_sort_def) = cx.adt_sort_def_of(def_id)
2003        {
2004            format!("{}", adt_sort_def.struct_variant().field_names()[field as usize])
2005        } else if let FieldProj::RawPtr { field } = proj {
2006            field.name().to_string()
2007        } else {
2008            format!("{}", proj.field_idx())
2009        }
2010    }
2011
2012    impl Pretty for Constant {
2013        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2014            match self {
2015                Constant::Int(i) => w!(cx, f, "{i}"),
2016                Constant::BitVec(i, sz) => w!(cx, f, "bv({i}, {sz})"),
2017                Constant::Real(r) => w!(cx, f, "{}", ^r.0),
2018                Constant::Bool(b) => w!(cx, f, "{b}"),
2019                Constant::Str(sym) => w!(cx, f, "\"{sym}\""),
2020                Constant::Char(c) => w!(cx, f, "\'{c}\'"),
2021            }
2022        }
2023    }
2024
2025    impl Pretty for AliasReft {
2026        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2027            w!(cx, f, "<({:?}) as {:?}", &self.args[0], self.assoc_id.parent())?;
2028            let args = &self.args[1..];
2029            if !args.is_empty() {
2030                w!(cx, f, "<{:?}>", join!(", ", args))?;
2031            }
2032            w!(cx, f, ">::{}", ^self.assoc_id.name())
2033        }
2034    }
2035
2036    impl Pretty for Lambda {
2037        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2038            let vars = self.body.vars();
2039            // TODO: remove redundant vars; see Ty
2040            // let redundant_bvars = self.body.redundant_bvars().into_iter().collect();
2041            cx.with_bound_vars(vars, || {
2042                cx.fmt_bound_vars(false, "λ", vars, ". ", f)?;
2043                w!(cx, f, "{:?}", self.body.as_ref().skip_binder())
2044            })
2045        }
2046    }
2047
2048    impl Pretty for Var {
2049        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2050            match self {
2051                Var::Bound(debruijn, var) => cx.fmt_bound_reft(*debruijn, *var, f),
2052                Var::EarlyParam(var) => w!(cx, f, "{}", ^var.name),
2053                Var::Free(name) => w!(cx, f, "{:?}", ^name),
2054                Var::EVar(evar) => w!(cx, f, "{:?}", ^evar),
2055                Var::ConstGeneric(param) => w!(cx, f, "{}", ^param.name),
2056            }
2057        }
2058    }
2059
2060    impl Pretty for KVar {
2061        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2062            w!(cx, f, "{:?}", ^self.kvid)?;
2063            match cx.kvar_args {
2064                KVarArgs::All => {
2065                    w!(
2066                        cx,
2067                        f,
2068                        "({:?})[{:?}]",
2069                        join!(", ", self.self_args()),
2070                        join!(", ", self.scope())
2071                    )?;
2072                }
2073                KVarArgs::SelfOnly => w!(cx, f, "({:?})", join!(", ", self.self_args()))?,
2074                KVarArgs::Hide => {}
2075            }
2076            Ok(())
2077        }
2078    }
2079
2080    impl Pretty for WKVar {
2081        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2082            // Since we reuse KVId, we need to make the serialization custom.
2083            // Also we will not serialize the parameters for now.
2084            w!(cx, f, "$wk{}_{}", ^self.wkvid.id.index(), ^cx.tcx().def_path_str(self.wkvid.parent_fn))?;
2085            w!(cx, f, "({:?})[{:?}]", join!(", ", self.self_args()), join!(", ", self.scope()))?;
2086            Ok(())
2087        }
2088    }
2089
2090    impl Pretty for Path {
2091        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2092            w!(cx, f, "{:?}", &self.loc)?;
2093            for field in &self.projection {
2094                w!(cx, f, ".{}", ^u32::from(*field))?;
2095            }
2096            Ok(())
2097        }
2098    }
2099
2100    impl Pretty for Loc {
2101        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2102            match self {
2103                Loc::Local(local) => w!(cx, f, "{:?}", ^local),
2104                Loc::Var(var) => w!(cx, f, "{:?}", var),
2105            }
2106        }
2107    }
2108
2109    impl Pretty for BinOp {
2110        fn fmt(&self, _cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2111            match self {
2112                BinOp::Iff => w!(cx, f, "<=>"),
2113                BinOp::Imp => w!(cx, f, "=>"),
2114                BinOp::Or => w!(cx, f, "||"),
2115                BinOp::And => w!(cx, f, "&&"),
2116                BinOp::Eq => w!(cx, f, "=="),
2117                BinOp::Ne => w!(cx, f, "!="),
2118                BinOp::Gt(_) => w!(cx, f, ">"),
2119                BinOp::Ge(_) => w!(cx, f, ">="),
2120                BinOp::Lt(_) => w!(cx, f, "<"),
2121                BinOp::Le(_) => w!(cx, f, "<="),
2122                BinOp::Add(_) => w!(cx, f, "+"),
2123                BinOp::Sub(_) => w!(cx, f, "-"),
2124                BinOp::Mul(_) => w!(cx, f, "*"),
2125                BinOp::Div(_) => w!(cx, f, "/"),
2126                BinOp::Mod(_) => w!(cx, f, "mod"),
2127                BinOp::BitAnd(_) => w!(cx, f, "&"),
2128                BinOp::BitOr(_) => w!(cx, f, "|"),
2129                BinOp::BitXor(_) => w!(cx, f, "^"),
2130                BinOp::BitShl(_) => w!(cx, f, "<<"),
2131                BinOp::BitShr(_) => w!(cx, f, ">>"),
2132            }
2133        }
2134    }
2135
2136    impl Pretty for UnOp {
2137        fn fmt(&self, _cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2138            match self {
2139                UnOp::Not => w!(cx, f, "!"),
2140                UnOp::Neg => w!(cx, f, "-"),
2141            }
2142        }
2143    }
2144
2145    impl_debug_with_default_cx!(Expr, Loc, Path, Var, KVar, WKVar, Lambda, AliasReft);
2146
2147    impl PrettyNested for Lambda {
2148        fn fmt_nested(&self, cx: &PrettyCx) -> Result<NestedString, fmt::Error> {
2149            // TODO: remove redundant vars; see Ty
2150            cx.nested_with_bound_vars("λ", self.body.vars(), None, |prefix| {
2151                let expr_d = self.body.skip_binder_ref().fmt_nested(cx)?;
2152                let text = format!("{}{}", prefix, expr_d.text);
2153                Ok(NestedString { text, children: expr_d.children, key: None })
2154            })
2155        }
2156    }
2157
2158    pub fn aggregate_nested(
2159        cx: &PrettyCx,
2160        ctor: &Ctor,
2161        flds: &[Expr],
2162        is_named: bool,
2163    ) -> Result<NestedString, fmt::Error> {
2164        let mut text =
2165            if is_named && ctor.is_enum() { format_cx!(cx, "{:?}", ctor) } else { "".to_string() };
2166        if flds.is_empty() {
2167            // No fields, no index
2168            Ok(NestedString { text, children: None, key: None })
2169        } else if flds.len() == 1 {
2170            // Single field, inline index
2171            text += &flds[0].fmt_nested(cx)?.text;
2172            Ok(NestedString { text, children: None, key: None })
2173        } else {
2174            let keys = if let Some((def_id, variant_idx)) = ctor.def_id_and_variant()
2175                && let Some(adt_sort_def) = cx.adt_sort_def_of(def_id)
2176            {
2177                adt_sort_def
2178                    .variant(variant_idx)
2179                    .field_names()
2180                    .iter()
2181                    .map(|name| format!("{name}"))
2182                    .collect_vec()
2183            } else if matches!(ctor, Ctor::RawPtr) {
2184                RawPtrField::iter()
2185                    .map(|field| field.name().to_string())
2186                    .collect_vec()
2187            } else {
2188                (0..flds.len()).map(|i| format!("arg{i}")).collect_vec()
2189            };
2190            // Multiple fields, nested index
2191            text += "{..}";
2192            let mut children = vec![];
2193            for (key, fld) in iter::zip(keys, flds) {
2194                let fld_d = fld.fmt_nested(cx)?;
2195                children.push(NestedString { key: Some(key), ..fld_d });
2196            }
2197            Ok(NestedString { text, children: Some(children), key: None })
2198        }
2199    }
2200
2201    impl PrettyNested for Name {
2202        fn fmt_nested(&self, cx: &PrettyCx) -> Result<NestedString, fmt::Error> {
2203            let text = cx.pretty_var_env.get(&PrettyVar::Local(*self));
2204            Ok(NestedString { text, key: None, children: None })
2205        }
2206    }
2207
2208    impl PrettyNested for Expr {
2209        fn fmt_nested(&self, cx: &PrettyCx) -> Result<NestedString, fmt::Error> {
2210            let e = if cx.simplify_exprs {
2211                self.simplify(&SnapshotMap::default())
2212            } else {
2213                self.clone()
2214            };
2215            match e.kind() {
2216                ExprKind::Var(Var::Free(name)) => name.fmt_nested(cx),
2217                ExprKind::Var(..)
2218                | ExprKind::Local(..)
2219                | ExprKind::Constant(..)
2220                | ExprKind::ConstDefId(..)
2221                | ExprKind::Hole(..)
2222                | ExprKind::GlobalFunc(..)
2223                | ExprKind::InternalFunc(..) => debug_nested(cx, &e),
2224                ExprKind::WKVar(..) => debug_nested(cx, &e),
2225                ExprKind::KVar(kvar) => {
2226                    let kv = format!("{:?}", kvar.kvid);
2227                    let mut strs = vec![kv];
2228                    for arg in &kvar.args {
2229                        strs.push(arg.fmt_nested(cx)?.text);
2230                    }
2231                    let text = format!("##[{}]##", strs.join("##"));
2232                    Ok(NestedString { text, children: None, key: None })
2233                }
2234                ExprKind::IfThenElse(p, e1, e2) => {
2235                    let p_d = p.fmt_nested(cx)?;
2236                    let e1_d = e1.fmt_nested(cx)?;
2237                    let e2_d = e2.fmt_nested(cx)?;
2238                    let text = format!("(if {} then {} else {})", p_d.text, e1_d.text, e2_d.text);
2239                    let children = float_children(vec![p_d.children, e1_d.children, e2_d.children]);
2240                    Ok(NestedString { text, children, key: None })
2241                }
2242                ExprKind::BinaryOp(op, e1, e2) => {
2243                    let e1_d = e1.fmt_nested(cx)?;
2244                    let e2_d = e2.fmt_nested(cx)?;
2245                    let e1_text = if should_parenthesize(op, e1) {
2246                        format!("({})", e1_d.text)
2247                    } else {
2248                        e1_d.text
2249                    };
2250                    let e2_text = if should_parenthesize(op, e2) {
2251                        format!("({})", e2_d.text)
2252                    } else {
2253                        e2_d.text
2254                    };
2255                    let op_d = debug_nested(cx, op)?;
2256                    let op_text = if matches!(op, BinOp::Div(_)) {
2257                        op_d.text
2258                    } else {
2259                        format!(" {} ", op_d.text)
2260                    };
2261                    let text = format!("{e1_text}{op_text}{e2_text}");
2262                    let children = float_children(vec![e1_d.children, e2_d.children]);
2263                    Ok(NestedString { text, children, key: None })
2264                }
2265                ExprKind::UnaryOp(op, e) => {
2266                    let e_d = e.fmt_nested(cx)?;
2267                    let op_d = debug_nested(cx, op)?;
2268                    let text = if e.is_atom() {
2269                        format!("{}{}", op_d.text, e_d.text)
2270                    } else {
2271                        format!("{}({})", op_d.text, e_d.text)
2272                    };
2273                    Ok(NestedString { text, children: e_d.children, key: None })
2274                }
2275                ExprKind::FieldProj(e, proj) if let ExprKind::Ctor(_, _) = e.kind() => {
2276                    // special case to avoid printing `{n:12}.n` as `12.n` but instead, just print `12`
2277                    // TODO: maintain an invariant that `FieldProj` never has a Ctor as first argument (as always reduced)
2278                    e.proj_and_reduce(*proj).fmt_nested(cx)
2279                }
2280                ExprKind::FieldProj(e, proj) => {
2281                    let e_d = e.fmt_nested(cx)?;
2282                    let text = if e.is_atom() {
2283                        format!("{}.{}", e_d.text, fmt_field_proj(cx, *proj))
2284                    } else {
2285                        format!("({}).{}", e_d.text, fmt_field_proj(cx, *proj))
2286                    };
2287                    Ok(NestedString { text, children: e_d.children, key: None })
2288                }
2289                ExprKind::Tuple(flds) => {
2290                    let mut texts = vec![];
2291                    let mut kidss = vec![];
2292                    for e in flds {
2293                        let e_d = e.fmt_nested(cx)?;
2294                        texts.push(e_d.text);
2295                        kidss.push(e_d.children);
2296                    }
2297                    let text = if let [e] = &texts[..] {
2298                        format!("({e},)")
2299                    } else {
2300                        format!("({})", texts.join(", "))
2301                    };
2302                    let children = float_children(kidss);
2303                    Ok(NestedString { text, children, key: None })
2304                }
2305                ExprKind::Ctor(ctor, flds) => aggregate_nested(cx, ctor, flds, true),
2306                ExprKind::IsCtor(def_id, variant_idx, idx) => {
2307                    let text = format!("is::{:?}::{:?}( {:?} )", def_id, variant_idx, idx);
2308                    Ok(NestedString { text, children: None, key: None })
2309                }
2310                ExprKind::PathProj(e, field) => {
2311                    let e_d = e.fmt_nested(cx)?;
2312                    let text = if e.is_atom() {
2313                        format!("{}.{:?}", e_d.text, field)
2314                    } else {
2315                        format!("({}).{:?}", e_d.text, field)
2316                    };
2317                    Ok(NestedString { text, children: e_d.children, key: None })
2318                }
2319                ExprKind::Alias(alias, args) => {
2320                    let mut texts = vec![];
2321                    let mut kidss = vec![];
2322                    for arg in args {
2323                        let arg_d = arg.fmt_nested(cx)?;
2324                        texts.push(arg_d.text);
2325                        kidss.push(arg_d.children);
2326                    }
2327                    let text = format_cx!(cx, "{:?}({:?})", alias, texts.join(", "));
2328                    let children = float_children(kidss);
2329                    Ok(NestedString { text, children, key: None })
2330                }
2331                ExprKind::App(func, _, args) => {
2332                    let func_d = func.fmt_nested(cx)?;
2333                    let mut texts = vec![];
2334                    let mut kidss = vec![func_d.children];
2335                    for arg in args {
2336                        let arg_d = arg.fmt_nested(cx)?;
2337                        texts.push(arg_d.text);
2338                        kidss.push(arg_d.children);
2339                    }
2340                    let text = if func.is_atom() {
2341                        format!("{}({})", func_d.text, texts.join(", "))
2342                    } else {
2343                        format!("({})({})", func_d.text, texts.join(", "))
2344                    };
2345                    let children = float_children(kidss);
2346                    Ok(NestedString { text, children, key: None })
2347                }
2348                ExprKind::Abs(lambda) => lambda.fmt_nested(cx),
2349                ExprKind::Let(init, body) => {
2350                    // FIXME this is very wrong!
2351                    cx.nested_with_bound_vars("let", body.vars(), None, |prefix| {
2352                        let body = body.skip_binder_ref().fmt_nested(cx)?;
2353                        let text = format!("{:?} {}{}", init, prefix, body.text);
2354                        Ok(NestedString { text, children: body.children, key: None })
2355                    })
2356                }
2357                ExprKind::Quant(kind, dom, body) => {
2358                    let left = match kind {
2359                        fhir::QuantKind::Forall => "∀",
2360                        fhir::QuantKind::Exists => "∃",
2361                    };
2362                    let right = Some(format!(" {:?}", dom));
2363
2364                    cx.nested_with_bound_vars(left, body.vars(), right, |all_str| {
2365                        let expr_d = body.as_ref().skip_binder().fmt_nested(cx)?;
2366                        let text = format!("{}{}", all_str, expr_d.text);
2367                        Ok(NestedString { text, children: expr_d.children, key: None })
2368                    })
2369                }
2370            }
2371        }
2372    }
2373}
2374
2375impl Expr {
2376    /// Applies transformations to simplify and humanize canonical Z3 outputs.
2377    pub fn prettify(&self) -> Self {
2378        self.fold_constants()
2379            .push_negations()
2380            .simplify_arithmetic()
2381            .simplify_bounds()
2382    }
2383
2384    /// Evaluates expressions with constant operands recursively.
2385    pub fn fold_constants(&self) -> Self {
2386        let span = self.span();
2387        match self.kind() {
2388            ExprKind::UnaryOp(op, a) => {
2389                let a_fold = a.fold_constants();
2390
2391                if let ExprKind::Constant(c) = a_fold.kind() {
2392                    match (op, c) {
2393                        (UnOp::Not, Constant::Bool(b)) => {
2394                            return Expr::constant(Constant::Bool(!b)).at_opt(span);
2395                        }
2396                        (UnOp::Neg, Constant::Int(n)) => {
2397                            return Expr::constant(Constant::Int(n.neg())).at_opt(span);
2398                        }
2399                        _ => {}
2400                    }
2401                }
2402                Expr::unary_op(*op, a_fold).at_opt(span)
2403            }
2404
2405            ExprKind::BinaryOp(op, a, b) => {
2406                let a_fold = a.fold_constants();
2407                let b_fold = b.fold_constants();
2408
2409                if let (ExprKind::Constant(c1), ExprKind::Constant(c2)) =
2410                    (a_fold.kind(), b_fold.kind())
2411                {
2412                    // 1. Leverage existing const_op logic (handles Bools, Eq, Ne, and Int comparisons)
2413                    if let Some(c3) = Expr::const_op(op, c1, c2) {
2414                        return Expr::constant(c3).at_opt(span);
2415                    }
2416
2417                    // 2. Handle integer arithmetic
2418                    if let (Constant::Int(n1), Constant::Int(n2)) = (c1, c2) {
2419                        let result = match op {
2420                            BinOp::Add(_) => n1.checked_add(n2),
2421                            BinOp::Sub(_) => n1.checked_sub(n2),
2422                            BinOp::Mul(_) => n1.checked_mul(n2),
2423                            BinOp::Div(_) => n1.checked_div(n2),
2424                            BinOp::Mod(_) => n1.checked_rem(n2),
2425                            _ => None,
2426                        };
2427
2428                        if let Some(res) = result {
2429                            return Expr::constant(Constant::Int(res)).at_opt(span);
2430                        }
2431                    }
2432                }
2433
2434                Expr::binary_op(op.clone(), a_fold, b_fold).at_opt(span)
2435            }
2436
2437            ExprKind::IfThenElse(p, e1, e2) => {
2438                let p_fold = p.fold_constants();
2439
2440                // Short-circuit the ITE if the predicate is a constant boolean
2441                if let ExprKind::Constant(Constant::Bool(b)) = p_fold.kind() {
2442                    if *b {
2443                        return e1.fold_constants().at_opt(span);
2444                    } else {
2445                        return e2.fold_constants().at_opt(span);
2446                    }
2447                }
2448
2449                Expr::ite(p_fold, e1.fold_constants(), e2.fold_constants()).at_opt(span)
2450            }
2451
2452            // Other variants (Tuple, App, FieldProj, etc.) recursively fall through
2453            // untouched to avoid making assumptions about how your inner types construct.
2454            _ => self.clone(),
2455        }
2456    }
2457
2458    /// Pass 1: Eliminate `Not` over inequalities and boolean operations.
2459    pub fn push_negations(&self) -> Self {
2460        let span = self.span();
2461        match self.kind() {
2462            ExprKind::UnaryOp(UnOp::Not, inner) => {
2463                match inner.kind() {
2464                    // Double negation
2465                    ExprKind::UnaryOp(UnOp::Not, a) => a.push_negations().at_opt(span),
2466
2467                    // Flipped Inequalities
2468                    ExprKind::BinaryOp(BinOp::Le(s), a, b) => {
2469                        Expr::binary_op(
2470                            BinOp::Gt(s.clone()),
2471                            a.push_negations(),
2472                            b.push_negations(),
2473                        )
2474                        .at_opt(span)
2475                    }
2476                    ExprKind::BinaryOp(BinOp::Lt(s), a, b) => {
2477                        Expr::binary_op(
2478                            BinOp::Ge(s.clone()),
2479                            a.push_negations(),
2480                            b.push_negations(),
2481                        )
2482                        .at_opt(span)
2483                    }
2484                    ExprKind::BinaryOp(BinOp::Ge(s), a, b) => {
2485                        Expr::binary_op(
2486                            BinOp::Lt(s.clone()),
2487                            a.push_negations(),
2488                            b.push_negations(),
2489                        )
2490                        .at_opt(span)
2491                    }
2492                    ExprKind::BinaryOp(BinOp::Gt(s), a, b) => {
2493                        Expr::binary_op(
2494                            BinOp::Le(s.clone()),
2495                            a.push_negations(),
2496                            b.push_negations(),
2497                        )
2498                        .at_opt(span)
2499                    }
2500
2501                    // Equalities
2502                    ExprKind::BinaryOp(BinOp::Eq, a, b) => {
2503                        Expr::binary_op(BinOp::Ne, a.push_negations(), b.push_negations())
2504                            .at_opt(span)
2505                    }
2506                    ExprKind::BinaryOp(BinOp::Ne, a, b) => {
2507                        Expr::binary_op(BinOp::Eq, a.push_negations(), b.push_negations())
2508                            .at_opt(span)
2509                    }
2510
2511                    // De Morgan's
2512                    ExprKind::BinaryOp(BinOp::Or, a, b) => {
2513                        Expr::binary_op(
2514                            BinOp::And,
2515                            a.not().push_negations(),
2516                            b.not().push_negations(),
2517                        )
2518                        .at_opt(span)
2519                    }
2520                    ExprKind::BinaryOp(BinOp::And, a, b) => {
2521                        Expr::binary_op(
2522                            BinOp::Or,
2523                            a.not().push_negations(),
2524                            b.not().push_negations(),
2525                        )
2526                        .at_opt(span)
2527                    }
2528
2529                    // Otherwise just wrap and stop
2530                    _ => Expr::unary_op(UnOp::Not, inner.push_negations()).at_opt(span),
2531                }
2532            }
2533            ExprKind::BinaryOp(op, a, b) => {
2534                Expr::binary_op(op.clone(), a.push_negations(), b.push_negations()).at_opt(span)
2535            }
2536            ExprKind::UnaryOp(op, a) => Expr::unary_op(*op, a.push_negations()).at_opt(span),
2537            ExprKind::IfThenElse(p, e1, e2) => {
2538                Expr::ite(p.push_negations(), e1.push_negations(), e2.push_negations()).at_opt(span)
2539            }
2540            _ => self.clone(),
2541        }
2542    }
2543
2544    /// Pass 2: Clean up canonicalized arithmetic (e.g. `b0 * -1 + b1` -> `b1 - b0`)
2545    pub fn simplify_arithmetic(&self) -> Self {
2546        let span = self.span();
2547        match self.kind() {
2548            ExprKind::BinaryOp(BinOp::Mul(s), a, b) => {
2549                let a_simp = a.simplify_arithmetic();
2550                let b_simp = b.simplify_arithmetic();
2551
2552                let is_minus_one = |e: &Expr| matches!(e.kind(), ExprKind::Constant(c) if *c == Constant::from(-1));
2553
2554                if is_minus_one(&b_simp) {
2555                    return a_simp.neg().at_opt(span);
2556                }
2557                if is_minus_one(&a_simp) {
2558                    return b_simp.neg().at_opt(span);
2559                }
2560
2561                Expr::binary_op(BinOp::Mul(s.clone()), a_simp, b_simp).at_opt(span)
2562            }
2563            ExprKind::BinaryOp(BinOp::Add(s), a, b) => {
2564                let a_simp = a.simplify_arithmetic();
2565                let b_simp = b.simplify_arithmetic();
2566
2567                if let ExprKind::UnaryOp(UnOp::Neg, x) = a_simp.kind() {
2568                    return Expr::binary_op(BinOp::Sub(s.clone()), b_simp, x.clone()).at_opt(span);
2569                }
2570                if let ExprKind::UnaryOp(UnOp::Neg, y) = b_simp.kind() {
2571                    return Expr::binary_op(BinOp::Sub(s.clone()), a_simp, y.clone()).at_opt(span);
2572                }
2573
2574                Expr::binary_op(BinOp::Add(s.clone()), a_simp, b_simp).at_opt(span)
2575            }
2576            ExprKind::BinaryOp(op, a, b) => {
2577                Expr::binary_op(op.clone(), a.simplify_arithmetic(), b.simplify_arithmetic())
2578                    .at_opt(span)
2579            }
2580            ExprKind::UnaryOp(op, a) => Expr::unary_op(*op, a.simplify_arithmetic()).at_opt(span),
2581            ExprKind::IfThenElse(p, e1, e2) => {
2582                Expr::ite(
2583                    p.simplify_arithmetic(),
2584                    e1.simplify_arithmetic(),
2585                    e2.simplify_arithmetic(),
2586                )
2587                .at_opt(span)
2588            }
2589            _ => self.clone(),
2590        }
2591    }
2592
2593    /// Pass 3: Integer shifts and inequality rearrangement
2594    pub fn simplify_bounds(&self) -> Self {
2595        let span = self.span();
2596        match self.kind() {
2597            ExprKind::BinaryOp(op, a, b) => {
2598                let a_simp = a.simplify_bounds();
2599                let b_simp = b.simplify_bounds();
2600
2601                let is_minus_one = |e: &Expr| matches!(e.kind(), ExprKind::Constant(c) if *c == Constant::from(-1));
2602                let is_zero =
2603                    |e: &Expr| matches!(e.kind(), ExprKind::Constant(c) if *c == Constant::from(0));
2604
2605                // Important: this logic only applies if the operators are specifically typed for integers
2606                match op {
2607                    BinOp::Gt(Sort::Int) => {
2608                        // X > -1  ==>  X >= 0
2609                        if is_minus_one(&b_simp) {
2610                            return Expr::binary_op(BinOp::Ge(Sort::Int), a_simp, Expr::zero())
2611                                .at_opt(span)
2612                                .simplify_bounds();
2613                        }
2614                        // X - Y > 0  ==>  X > Y
2615                        if is_zero(&b_simp)
2616                            && let ExprKind::BinaryOp(BinOp::Sub(s_sub), x, y) = a_simp.kind()
2617                        {
2618                            return Expr::binary_op(BinOp::Gt(s_sub.clone()), x.clone(), y.clone())
2619                                .at_opt(span);
2620                        }
2621                    }
2622                    BinOp::Ge(Sort::Int) => {
2623                        // X - Y >= 0  ==>  X >= Y
2624                        if is_zero(&b_simp)
2625                            && let ExprKind::BinaryOp(BinOp::Sub(s_sub), x, y) = a_simp.kind()
2626                        {
2627                            return Expr::binary_op(BinOp::Ge(s_sub.clone()), x.clone(), y.clone())
2628                                .at_opt(span);
2629                        }
2630                    }
2631                    BinOp::Lt(Sort::Int) => {
2632                        // X - Y < 0  ==>  X < Y
2633                        if is_zero(&b_simp)
2634                            && let ExprKind::BinaryOp(BinOp::Sub(s_sub), x, y) = a_simp.kind()
2635                        {
2636                            return Expr::binary_op(BinOp::Lt(s_sub.clone()), x.clone(), y.clone())
2637                                .at_opt(span);
2638                        }
2639                    }
2640                    BinOp::Le(Sort::Int) => {
2641                        // X <= -1  ==>  X < 0
2642                        if is_minus_one(&b_simp) {
2643                            return Expr::binary_op(BinOp::Lt(Sort::Int), a_simp, Expr::zero())
2644                                .at_opt(span)
2645                                .simplify_bounds();
2646                        }
2647                        // X - Y <= 0  ==>  X <= Y
2648                        if is_zero(&b_simp)
2649                            && let ExprKind::BinaryOp(BinOp::Sub(s_sub), x, y) = a_simp.kind()
2650                        {
2651                            return Expr::binary_op(BinOp::Le(s_sub.clone()), x.clone(), y.clone())
2652                                .at_opt(span);
2653                        }
2654                    }
2655                    _ => {}
2656                }
2657
2658                Expr::binary_op(op.clone(), a_simp, b_simp).at_opt(span)
2659            }
2660            ExprKind::UnaryOp(op, a) => Expr::unary_op(*op, a.simplify_bounds()).at_opt(span),
2661            ExprKind::IfThenElse(p, e1, e2) => {
2662                Expr::ite(p.simplify_bounds(), e1.simplify_bounds(), e2.simplify_bounds())
2663                    .at_opt(span)
2664            }
2665            _ => self.clone(),
2666        }
2667    }
2668}