flux_infer/
fixpoint_encoding.rs

1//! Encoding of the refinement tree into a fixpoint constraint.
2
3use std::{hash::Hash, iter};
4
5use fixpoint::AdtId;
6use flux_common::{
7    bug,
8    cache::QueryCache,
9    dbg,
10    index::{IndexGen, IndexVec},
11    iter::IterExt,
12    span_bug, tracked_span_bug,
13};
14use flux_config::{self as config};
15use flux_errors::Errors;
16use flux_middle::{
17    FixpointQueryKind,
18    def_id::{FluxDefId, MaybeExternId},
19    def_id_to_string,
20    global_env::GlobalEnv,
21    queries::QueryResult,
22    rty::{self, ESpan, GenericArgsExt, InternalFuncKind, Lambda, List, SpecFuncKind, VariantIdx},
23    timings::{self, TimingKind},
24};
25use itertools::Itertools;
26use liquid_fixpoint::{FixpointResult, SmtSolver};
27use rustc_data_structures::{
28    fx::{FxIndexMap, FxIndexSet},
29    unord::{UnordMap, UnordSet},
30};
31use rustc_hir::def_id::{DefId, LocalDefId};
32use rustc_index::newtype_index;
33use rustc_infer::infer::TyCtxtInferExt as _;
34use rustc_middle::ty::TypingMode;
35use rustc_span::Span;
36use rustc_type_ir::{BoundVar, DebruijnIndex};
37use serde::{Deserialize, Deserializer, Serialize};
38
39use crate::{
40    fixpoint_qualifiers::FIXPOINT_QUALIFIERS, lean_encoding::LeanEncoder,
41    projections::structurally_normalize_expr,
42};
43
44pub mod fixpoint {
45    use std::fmt;
46
47    use flux_middle::rty::{EarlyReftParam, Real};
48    use liquid_fixpoint::{FixpointFmt, Identifier};
49    use rustc_abi::VariantIdx;
50    use rustc_index::newtype_index;
51    use rustc_middle::ty::ParamConst;
52    use rustc_span::Symbol;
53
54    newtype_index! {
55        pub struct KVid {}
56    }
57
58    impl Identifier for KVid {
59        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60            write!(f, "k{}", self.as_u32())
61        }
62    }
63
64    newtype_index! {
65        pub struct LocalVar {}
66    }
67
68    newtype_index! {
69        pub struct GlobalVar {}
70    }
71
72    newtype_index! {
73        /// Unique id assigned to each [`flux_middle::rty::AdtSortDef`] that needs to be encoded
74        /// into fixpoint
75        pub struct AdtId {}
76    }
77
78    #[derive(Hash, Copy, Clone, Debug, PartialEq, Eq)]
79    pub enum Var {
80        Underscore,
81        Global(GlobalVar, Option<Symbol>),
82        Local(LocalVar),
83        DataCtor(AdtId, VariantIdx),
84        TupleCtor { arity: usize },
85        TupleProj { arity: usize, field: u32 },
86        DataProj { adt_id: AdtId, /* variant_idx: VariantIdx, */ field: u32 },
87        UIFRel(BinRel),
88        Param(EarlyReftParam),
89        ConstGeneric(ParamConst),
90    }
91
92    impl From<GlobalVar> for Var {
93        fn from(v: GlobalVar) -> Self {
94            Self::Global(v, None)
95        }
96    }
97
98    impl From<LocalVar> for Var {
99        fn from(v: LocalVar) -> Self {
100            Self::Local(v)
101        }
102    }
103
104    impl Identifier for Var {
105        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106            match self {
107                Var::Global(v, None) => write!(f, "c{}", v.as_u32()),
108                Var::Global(v, Some(sym)) => write!(f, "f${}${}", sym, v.as_u32()),
109                Var::Local(v) => write!(f, "a{}", v.as_u32()),
110                Var::DataCtor(adt_id, variant_idx) => {
111                    write!(f, "mkadt{}${}", adt_id.as_u32(), variant_idx.as_u32())
112                }
113                Var::TupleCtor { arity } => write!(f, "mktuple{arity}"),
114                Var::TupleProj { arity, field } => write!(f, "tuple{arity}${field}"),
115                Var::DataProj { adt_id, field } => write!(f, "fld{}${field}", adt_id.as_u32()),
116                Var::UIFRel(BinRel::Gt) => write!(f, "gt"),
117                Var::UIFRel(BinRel::Ge) => write!(f, "ge"),
118                Var::UIFRel(BinRel::Lt) => write!(f, "lt"),
119                Var::UIFRel(BinRel::Le) => write!(f, "le"),
120                // these are actually not necessary because equality is interpreted for all sorts
121                Var::UIFRel(BinRel::Eq) => write!(f, "eq"),
122                Var::UIFRel(BinRel::Ne) => write!(f, "ne"),
123                Var::Underscore => write!(f, "_$"), // To avoid clashing with `_` used for `app (_ bv_op n)` for parametric SMT ops
124                Var::ConstGeneric(param) => {
125                    write!(f, "constgen${}${}", param.name, param.index)
126                }
127                Var::Param(param) => {
128                    write!(f, "reftgen${}${}", param.name, param.index)
129                }
130            }
131        }
132    }
133
134    #[derive(Clone, Hash, Debug, PartialEq, Eq)]
135    pub enum DataSort {
136        Tuple(usize),
137        Adt(AdtId),
138    }
139
140    impl Identifier for DataSort {
141        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142            match self {
143                DataSort::Tuple(arity) => {
144                    write!(f, "Tuple{arity}")
145                }
146                DataSort::Adt(adt_id) => {
147                    write!(f, "Adt{}", adt_id.as_u32())
148                }
149            }
150        }
151    }
152
153    #[derive(Hash, Clone, Debug)]
154    pub struct SymStr(pub Symbol);
155
156    #[cfg(feature = "rust-fixpoint")]
157    impl FixpointFmt for SymStr {
158        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159            write!(f, "{}", self.0)
160        }
161    }
162
163    #[cfg(not(feature = "rust-fixpoint"))]
164    impl FixpointFmt for SymStr {
165        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166            write!(f, "\"{}\"", self.0)
167        }
168    }
169
170    liquid_fixpoint::declare_types! {
171        type Sort = DataSort;
172        type KVar = KVid;
173        type Var = Var;
174        type Decimal = Real;
175        type String = SymStr;
176        type Tag = super::TagIdx;
177    }
178    pub use fixpoint_generated::*;
179}
180
181newtype_index! {
182    #[debug_format = "TagIdx({})"]
183    pub struct TagIdx {}
184}
185
186impl Serialize for TagIdx {
187    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
188        self.as_u32().serialize(serializer)
189    }
190}
191
192impl<'de> Deserialize<'de> for TagIdx {
193    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
194        let idx = usize::deserialize(deserializer)?;
195        Ok(TagIdx::from_u32(idx as u32))
196    }
197}
198
199/// Keep track of all the data sorts that we need to define in fixpoint to encode the constraint.
200#[derive(Default)]
201struct SortEncodingCtxt {
202    /// Set of all the tuple arities that need to be defined
203    tuples: UnordSet<usize>,
204    /// Set of all the [`AdtDefSortDef`](flux_middle::rty::AdtSortDef) that need to be declared as
205    /// Fixpoint data-decls
206    adt_sorts: FxIndexSet<DefId>,
207}
208
209impl SortEncodingCtxt {
210    fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
211        match sort {
212            rty::Sort::Int => fixpoint::Sort::Int,
213            rty::Sort::Real => fixpoint::Sort::Real,
214            rty::Sort::Bool => fixpoint::Sort::Bool,
215            rty::Sort::Str => fixpoint::Sort::Str,
216            rty::Sort::Char => fixpoint::Sort::Int,
217            rty::Sort::BitVec(size) => fixpoint::Sort::BitVec(Box::new(bv_size_to_fixpoint(*size))),
218            // There's no way to declare opaque sorts in the fixpoint horn syntax so we encode user
219            // declared opaque sorts, type parameter sorts, and (unormalizable) type alias sorts as
220            // integers. Well-formedness should ensure values of these sorts are used "opaquely",
221            // i.e., the only values of these sorts are variables.
222            rty::Sort::App(rty::SortCtor::User { .. }, _)
223            | rty::Sort::Param(_)
224            | rty::Sort::Alias(rty::AliasKind::Opaque | rty::AliasKind::Projection, ..) => {
225                fixpoint::Sort::Int
226            }
227            rty::Sort::App(rty::SortCtor::Set, args) => {
228                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
229                fixpoint::Sort::App(fixpoint::SortCtor::Set, args)
230            }
231            rty::Sort::App(rty::SortCtor::Map, args) => {
232                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
233                fixpoint::Sort::App(fixpoint::SortCtor::Map, args)
234            }
235            rty::Sort::App(rty::SortCtor::Adt(sort_def), args) => {
236                if let Some(variant) = sort_def.opt_struct_variant() {
237                    let sorts = variant.field_sorts(args);
238                    // do not generate 1-tuples
239                    if let [sort] = &sorts[..] {
240                        self.sort_to_fixpoint(sort)
241                    } else {
242                        let adt_id = self.declare_adt(sort_def.did());
243                        let ctor = fixpoint::SortCtor::Data(fixpoint::DataSort::Adt(adt_id));
244                        let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
245                        fixpoint::Sort::App(ctor, args)
246                    }
247                } else {
248                    debug_assert!(args.is_empty());
249                    let adt_id = self.declare_adt(sort_def.did());
250                    fixpoint::Sort::App(
251                        fixpoint::SortCtor::Data(fixpoint::DataSort::Adt(adt_id)),
252                        vec![],
253                    )
254                }
255            }
256            rty::Sort::Tuple(sorts) => {
257                // do not generate 1-tuples
258                if let [sort] = &sorts[..] {
259                    self.sort_to_fixpoint(sort)
260                } else {
261                    self.declare_tuple(sorts.len());
262                    let ctor = fixpoint::SortCtor::Data(fixpoint::DataSort::Tuple(sorts.len()));
263                    let args = sorts.iter().map(|s| self.sort_to_fixpoint(s)).collect();
264                    fixpoint::Sort::App(ctor, args)
265                }
266            }
267            rty::Sort::Func(sort) => self.func_sort_to_fixpoint(sort),
268            rty::Sort::Var(k) => fixpoint::Sort::Var(k.index()),
269            rty::Sort::Err
270            | rty::Sort::Infer(_)
271            | rty::Sort::Loc
272            | rty::Sort::Alias(rty::AliasKind::Free, _) => {
273                tracked_span_bug!("unexpected sort `{sort:?}`")
274            }
275        }
276    }
277
278    fn func_sort_to_fixpoint(&mut self, fsort: &rty::PolyFuncSort) -> fixpoint::Sort {
279        let params = fsort.params().len();
280        let fsort = fsort.skip_binders();
281        let output = self.sort_to_fixpoint(fsort.output());
282        fixpoint::Sort::mk_func(
283            params,
284            fsort.inputs().iter().map(|s| self.sort_to_fixpoint(s)),
285            output,
286        )
287    }
288
289    fn declare_tuple(&mut self, arity: usize) {
290        self.tuples.insert(arity);
291    }
292
293    pub fn declare_adt(&mut self, did: DefId) -> AdtId {
294        if let Some(idx) = self.adt_sorts.get_index_of(&did) {
295            AdtId::from_usize(idx)
296        } else {
297            let adt_id = AdtId::from_usize(self.adt_sorts.len());
298            self.adt_sorts.insert(did);
299            adt_id
300        }
301    }
302
303    fn append_adt_decls(
304        &mut self,
305        genv: GlobalEnv,
306        decls: &mut Vec<fixpoint::DataDecl>,
307    ) -> QueryResult {
308        // We iterate until we have processed all adt sorts because processing one adt sort may
309        // discover new adt sorts to process (e.g., if an adt field has an adt sort).
310        let mut idx = 0;
311        while let Some(adt_def_id) = self.adt_sorts.get_index(idx) {
312            let adt_id = AdtId::from_usize(idx);
313            let adt_sort_def = genv.adt_sort_def_of(adt_def_id)?;
314            decls.push(fixpoint::DataDecl {
315                name: fixpoint::DataSort::Adt(adt_id),
316                vars: adt_sort_def.param_count(),
317                ctors: adt_sort_def
318                    .variants()
319                    .iter_enumerated()
320                    .map(|(idx, variant)| {
321                        let name = fixpoint::Var::DataCtor(adt_id, idx);
322                        let fields = variant
323                            .field_sorts_instantiate_identity()
324                            .iter()
325                            .enumerate()
326                            .map(|(i, sort)| {
327                                fixpoint::DataField {
328                                    name: fixpoint::Var::DataProj { adt_id, field: i as u32 },
329                                    sort: self.sort_to_fixpoint(sort),
330                                }
331                            })
332                            .collect_vec();
333                        fixpoint::DataCtor { name, fields }
334                    })
335                    .collect(),
336            });
337            idx += 1;
338        }
339        Ok(())
340    }
341
342    fn append_tuple_decls(tuples: UnordSet<usize>, decls: &mut Vec<fixpoint::DataDecl>) {
343        decls.extend(
344            tuples
345                .into_items()
346                .into_sorted_stable_ord()
347                .into_iter()
348                .map(|arity| {
349                    fixpoint::DataDecl {
350                        name: fixpoint::DataSort::Tuple(arity),
351                        vars: arity,
352                        ctors: vec![fixpoint::DataCtor {
353                            name: fixpoint::Var::TupleCtor { arity },
354                            fields: (0..(arity as u32))
355                                .map(|field| {
356                                    fixpoint::DataField {
357                                        name: fixpoint::Var::TupleProj { arity, field },
358                                        sort: fixpoint::Sort::Var(field as usize),
359                                    }
360                                })
361                                .collect(),
362                        }],
363                    }
364                }),
365        );
366    }
367
368    fn into_data_decls(mut self, genv: GlobalEnv) -> QueryResult<Vec<fixpoint::DataDecl>> {
369        let mut decls = vec![];
370        self.append_adt_decls(genv, &mut decls)?;
371        Self::append_tuple_decls(self.tuples, &mut decls);
372        Ok(decls)
373    }
374}
375
376fn bv_size_to_fixpoint(size: rty::BvSize) -> fixpoint::Sort {
377    match size {
378        rty::BvSize::Fixed(size) => fixpoint::Sort::BvSize(size),
379        rty::BvSize::Param(_var) => {
380            // I think we could encode the size as a sort variable, but this would require some care
381            // because smtlib doesn't really support parametric sizes. Fixpoint is probably already
382            // too liberal about this and it'd be easy to make it crash.
383            // fixpoint::Sort::Var(var.index)
384            bug!("unexpected parametric bit-vector size")
385        }
386        rty::BvSize::Infer(_) => bug!("unexpected infer variable for bit-vector size"),
387    }
388}
389
390type FunDefMap = FxIndexMap<FluxDefId, fixpoint::Var>;
391type ConstMap<'tcx> = FxIndexMap<ConstKey<'tcx>, fixpoint::ConstDecl>;
392
393#[derive(Eq, Hash, PartialEq)]
394enum ConstKey<'tcx> {
395    Uif(FluxDefId),
396    RustConst(DefId),
397    Alias(FluxDefId, rustc_middle::ty::GenericArgsRef<'tcx>),
398    Lambda(Lambda),
399    PrimOp(rty::BinOp),
400    Cast(rty::Sort, rty::Sort),
401}
402
403pub struct FixpointCtxt<'genv, 'tcx, T: Eq + Hash> {
404    comments: Vec<String>,
405    genv: GlobalEnv<'genv, 'tcx>,
406    kvars: KVarGen,
407    scx: SortEncodingCtxt,
408    kcx: KVarEncodingCtxt,
409    ecx: ExprEncodingCtxt<'genv, 'tcx>,
410    tags: IndexVec<TagIdx, T>,
411    tags_inv: UnordMap<T, TagIdx>,
412}
413
414pub type FixQueryCache = QueryCache<FixpointResult<TagIdx>>;
415
416impl<'genv, 'tcx, Tag> FixpointCtxt<'genv, 'tcx, Tag>
417where
418    Tag: std::hash::Hash + Eq + Copy,
419{
420    pub fn new(genv: GlobalEnv<'genv, 'tcx>, def_id: MaybeExternId, kvars: KVarGen) -> Self {
421        Self {
422            comments: vec![],
423            kvars,
424            scx: SortEncodingCtxt::default(),
425            genv,
426            ecx: ExprEncodingCtxt::new(genv, def_id),
427            kcx: Default::default(),
428            tags: IndexVec::new(),
429            tags_inv: Default::default(),
430        }
431    }
432
433    pub fn check(
434        mut self,
435        cache: &mut FixQueryCache,
436        constraint: fixpoint::Constraint,
437        kind: FixpointQueryKind,
438        scrape_quals: bool,
439        solver: SmtSolver,
440    ) -> QueryResult<Vec<Tag>> {
441        // skip checking trivial constraints
442        if !constraint.is_concrete() {
443            self.ecx.errors.into_result()?;
444            return Ok(vec![]);
445        }
446        let def_span = self.ecx.def_span();
447        let def_id = self.ecx.def_id;
448
449        let kvars = self.kcx.into_fixpoint();
450
451        let (define_funs, define_constants) = self.ecx.define_funs(def_id, &mut self.scx)?;
452        let qualifiers = self
453            .ecx
454            .qualifiers_for(def_id.local_id(), &mut self.scx)?
455            .into_iter()
456            .chain(FIXPOINT_QUALIFIERS.iter().cloned())
457            .collect();
458
459        // Assuming values should happen after all encoding is done so we are sure we've collected
460        // all constants.
461        let constraint = self.ecx.assume_const_values(constraint, &mut self.scx)?;
462
463        let mut constants = self.ecx.const_map.into_values().collect_vec();
464        constants.extend(define_constants);
465
466        // The rust fixpoint implementation does not yet support polymorphic functions.
467        // For now we avoid including these by default so that cases where they are not needed can work.
468        // Should be removed when support is added.
469        #[cfg(not(feature = "rust-fixpoint"))]
470        for rel in fixpoint::BinRel::INEQUALITIES {
471            // ∀a. a -> a -> bool
472            let sort = fixpoint::Sort::mk_func(
473                1,
474                [fixpoint::Sort::Var(0), fixpoint::Sort::Var(0)],
475                fixpoint::Sort::Bool,
476            );
477            constants.push(fixpoint::ConstDecl {
478                name: fixpoint::Var::UIFRel(rel),
479                sort,
480                comment: None,
481            });
482        }
483
484        // We are done encoding expressions. Check if there are any errors.
485        self.ecx.errors.into_result()?;
486
487        let task = fixpoint::Task {
488            comments: self.comments,
489            constants,
490            kvars,
491            define_funs,
492            constraint,
493            qualifiers,
494            scrape_quals,
495            solver,
496            data_decls: self.scx.into_data_decls(self.genv)?,
497        };
498        if config::dump_constraint() {
499            dbg::dump_item_info(self.genv.tcx(), def_id.resolved_id(), "smt2", &task).unwrap();
500        }
501
502        match Self::run_task_with_cache(self.genv, task, def_id.resolved_id(), kind, cache) {
503            FixpointResult::Safe(_) => Ok(vec![]),
504            FixpointResult::Unsafe(_, errors) => {
505                Ok(errors
506                    .into_iter()
507                    .map(|err| self.tags[err.tag])
508                    .unique()
509                    .collect_vec())
510            }
511            FixpointResult::Crash(err) => span_bug!(def_span, "fixpoint crash: {err:?}"),
512        }
513    }
514
515    pub fn generate_and_check_lean_lemmas(
516        mut self,
517        constraint: fixpoint::Constraint,
518    ) -> QueryResult<()> {
519        let def_id = self.ecx.def_id;
520
521        if !self.kcx.into_fixpoint().is_empty() {
522            tracked_span_bug!("cannot generate lean lemmas for constraints with kvars");
523        }
524
525        let (define_funs, define_constants) = self.ecx.define_funs(def_id, &mut self.scx)?;
526
527        let constraint = self.ecx.assume_const_values(constraint, &mut self.scx)?;
528
529        let mut constants = self.ecx.const_map.into_values().collect_vec();
530        constants.extend(define_constants);
531
532        self.ecx.errors.into_result()?;
533
534        let lean_encoder = LeanEncoder::new(def_id, self.genv, define_funs, constants, constraint);
535        let lean_path = std::path::Path::new("./");
536        let project_name = "lean_proofs";
537        lean_encoder.check_proof(lean_path, project_name)
538    }
539
540    fn run_task_with_cache(
541        genv: GlobalEnv,
542        task: fixpoint::Task,
543        def_id: DefId,
544        kind: FixpointQueryKind,
545        cache: &mut FixQueryCache,
546    ) -> FixpointResult<TagIdx> {
547        let key = kind.task_key(genv.tcx(), def_id);
548
549        let hash = task.hash_with_default();
550
551        if config::is_cache_enabled()
552            && let Some(result) = cache.lookup(&key, hash)
553        {
554            return result.clone();
555        }
556        let result = timings::time_it(TimingKind::FixpointQuery(def_id, kind), || {
557            task.run()
558                .unwrap_or_else(|err| tracked_span_bug!("failed to run fixpoint: {err}"))
559        });
560
561        if config::is_cache_enabled() {
562            cache.insert(key, hash, result.clone());
563        }
564        result
565    }
566
567    fn tag_idx(&mut self, tag: Tag) -> TagIdx
568    where
569        Tag: std::fmt::Debug,
570    {
571        *self.tags_inv.entry(tag).or_insert_with(|| {
572            let idx = self.tags.push(tag);
573            self.comments.push(format!("Tag {idx}: {tag:?}"));
574            idx
575        })
576    }
577
578    pub(crate) fn with_name_map<R>(
579        &mut self,
580        name: rty::Name,
581        f: impl FnOnce(&mut Self, fixpoint::LocalVar) -> R,
582    ) -> R {
583        let fresh = self.ecx.local_var_env.insert_fvar_map(name);
584        let r = f(self, fresh);
585        self.ecx.local_var_env.remove_fvar_map(name);
586        r
587    }
588
589    pub(crate) fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
590        self.scx.sort_to_fixpoint(sort)
591    }
592
593    pub(crate) fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
594        self.ecx.var_to_fixpoint(var)
595    }
596
597    /// Encodes an expression in head position as a [`fixpoint::Constraint`] "peeling out"
598    /// implications and foralls.
599    ///
600    /// [`fixpoint::Constraint`]: liquid_fixpoint::Constraint
601    pub(crate) fn head_to_fixpoint(
602        &mut self,
603        expr: &rty::Expr,
604        mk_tag: impl Fn(Option<ESpan>) -> Tag + Copy,
605    ) -> QueryResult<fixpoint::Constraint>
606    where
607        Tag: std::fmt::Debug,
608    {
609        match expr.kind() {
610            rty::ExprKind::BinaryOp(rty::BinOp::And, ..) => {
611                // avoid creating nested conjunctions
612                let cstrs = expr
613                    .flatten_conjs()
614                    .into_iter()
615                    .map(|e| self.head_to_fixpoint(e, mk_tag))
616                    .try_collect()?;
617                Ok(fixpoint::Constraint::conj(cstrs))
618            }
619            rty::ExprKind::BinaryOp(rty::BinOp::Imp, e1, e2) => {
620                let (bindings, assumption) = self.assumption_to_fixpoint(e1)?;
621                let cstr = self.head_to_fixpoint(e2, mk_tag)?;
622                Ok(fixpoint::Constraint::foralls(bindings, mk_implies(assumption, cstr)))
623            }
624            rty::ExprKind::KVar(kvar) => {
625                let mut bindings = vec![];
626                let pred = self.kvar_to_fixpoint(kvar, &mut bindings)?;
627                Ok(fixpoint::Constraint::foralls(bindings, fixpoint::Constraint::Pred(pred, None)))
628            }
629            rty::ExprKind::ForAll(pred) => {
630                self.ecx
631                    .local_var_env
632                    .push_layer_with_fresh_names(pred.vars().len());
633                let cstr = self.head_to_fixpoint(pred.as_ref().skip_binder(), mk_tag)?;
634                let vars = self.ecx.local_var_env.pop_layer();
635
636                let bindings = iter::zip(vars, pred.vars())
637                    .map(|(var, kind)| {
638                        fixpoint::Bind {
639                            name: var.into(),
640                            sort: self.scx.sort_to_fixpoint(kind.expect_sort()),
641                            pred: fixpoint::Pred::TRUE,
642                        }
643                    })
644                    .collect_vec();
645
646                Ok(fixpoint::Constraint::foralls(bindings, cstr))
647            }
648            _ => {
649                let tag_idx = self.tag_idx(mk_tag(expr.span()));
650                let pred = fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?);
651                Ok(fixpoint::Constraint::Pred(pred, Some(tag_idx)))
652            }
653        }
654    }
655
656    /// Encodes an expression in assumptive position as a [`fixpoint::Pred`]. Returns the encoded
657    /// predicate and a list of bindings produced by ANF-ing kvars.
658    ///
659    /// [`fixpoint::Pred`]: liquid_fixpoint::Pred
660    pub(crate) fn assumption_to_fixpoint(
661        &mut self,
662        pred: &rty::Expr,
663    ) -> QueryResult<(Vec<fixpoint::Bind>, fixpoint::Pred)> {
664        let mut bindings = vec![];
665        let mut preds = vec![];
666        self.assumption_to_fixpoint_aux(pred, &mut bindings, &mut preds)?;
667        Ok((bindings, fixpoint::Pred::and(preds)))
668    }
669
670    /// Auxiliary function to merge nested conjunctions in a single predicate
671    fn assumption_to_fixpoint_aux(
672        &mut self,
673        expr: &rty::Expr,
674        bindings: &mut Vec<fixpoint::Bind>,
675        preds: &mut Vec<fixpoint::Pred>,
676    ) -> QueryResult {
677        match expr.kind() {
678            rty::ExprKind::BinaryOp(rty::BinOp::And, e1, e2) => {
679                self.assumption_to_fixpoint_aux(e1, bindings, preds)?;
680                self.assumption_to_fixpoint_aux(e2, bindings, preds)?;
681            }
682            rty::ExprKind::KVar(kvar) => {
683                preds.push(self.kvar_to_fixpoint(kvar, bindings)?);
684            }
685            rty::ExprKind::ForAll(_) => {
686                // If a forall appears in assumptive position replace it with true. This is sound
687                // because we are weakening the context, i.e., anything true without the assumption
688                // should remain true after adding it. Note that this relies on the predicate
689                // appearing negatively. This is guaranteed by the surface syntax because foralls
690                // can only appear at the top-level in a requires clause.
691                preds.push(fixpoint::Pred::TRUE);
692            }
693            _ => {
694                preds.push(fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?));
695            }
696        }
697        Ok(())
698    }
699
700    fn kvar_to_fixpoint(
701        &mut self,
702        kvar: &rty::KVar,
703        bindings: &mut Vec<fixpoint::Bind>,
704    ) -> QueryResult<fixpoint::Pred> {
705        let decl = self.kvars.get(kvar.kvid);
706        let kvids = self.kcx.encode(kvar.kvid, decl, &mut self.scx);
707
708        let all_args = iter::zip(&kvar.args, &decl.sorts)
709            .map(|(arg, sort)| self.ecx.imm(arg, sort, &mut self.scx, bindings))
710            .try_collect_vec()?;
711
712        // Fixpoint doesn't support kvars without arguments, which we do generate sometimes. To get
713        // around it, we encode `$k()` as ($k 0), or more precisely `(forall ((x int) (= x 0)) ... ($k x)`
714        // after ANF-ing.
715        if all_args.is_empty() {
716            let fresh = self.ecx.local_var_env.fresh_name();
717            let var = fixpoint::Var::Local(fresh);
718            bindings.push(fixpoint::Bind {
719                name: fresh.into(),
720                sort: fixpoint::Sort::Int,
721                pred: fixpoint::Pred::Expr(fixpoint::Expr::eq(
722                    fixpoint::Expr::Var(var),
723                    fixpoint::Expr::int(0),
724                )),
725            });
726            return Ok(fixpoint::Pred::KVar(kvids[0], vec![var]));
727        }
728
729        let kvars = kvids
730            .iter()
731            .enumerate()
732            .map(|(i, kvid)| {
733                let args = all_args[i..].to_vec();
734                fixpoint::Pred::KVar(*kvid, args)
735            })
736            .collect_vec();
737
738        Ok(fixpoint::Pred::And(kvars))
739    }
740}
741
742fn const_to_fixpoint(cst: rty::Constant) -> fixpoint::Expr {
743    match cst {
744        rty::Constant::Int(i) => {
745            if i.is_negative() {
746                fixpoint::Expr::Neg(Box::new(fixpoint::Constant::Numeral(i.abs()).into()))
747            } else {
748                fixpoint::Constant::Numeral(i.abs()).into()
749            }
750        }
751        rty::Constant::Real(r) => fixpoint::Constant::Decimal(r).into(),
752        rty::Constant::Bool(b) => fixpoint::Constant::Boolean(b).into(),
753        rty::Constant::Char(c) => fixpoint::Constant::Numeral(u128::from(c)).into(),
754        rty::Constant::Str(s) => fixpoint::Constant::String(fixpoint::SymStr(s)).into(),
755        rty::Constant::BitVec(i, size) => fixpoint::Constant::BitVec(i, size).into(),
756    }
757}
758
759struct FixpointKVar {
760    sorts: Vec<fixpoint::Sort>,
761    orig: rty::KVid,
762}
763
764/// During encoding into fixpoint we generate multiple fixpoint kvars per kvar in flux. A
765/// [`KVarEncodingCtxt`] is used to keep track of the state needed for this.
766#[derive(Default)]
767struct KVarEncodingCtxt {
768    /// List of all kvars that need to be defined in fixpoint
769    kvars: IndexVec<fixpoint::KVid, FixpointKVar>,
770    /// A mapping from [`rty::KVid`] to the list of [`fixpoint::KVid`]s encoding the kvar.
771    map: UnordMap<rty::KVid, Vec<fixpoint::KVid>>,
772}
773
774impl KVarEncodingCtxt {
775    fn encode(
776        &mut self,
777        kvid: rty::KVid,
778        decl: &KVarDecl,
779        scx: &mut SortEncodingCtxt,
780    ) -> &[fixpoint::KVid] {
781        self.map.entry(kvid).or_insert_with(|| {
782            let all_args = decl
783                .sorts
784                .iter()
785                .map(|s| scx.sort_to_fixpoint(s))
786                .collect_vec();
787
788            // See comment in `kvar_to_fixpoint`
789            if all_args.is_empty() {
790                let sorts = vec![fixpoint::Sort::Int];
791                let kvid = self.kvars.push(FixpointKVar::new(sorts, kvid));
792                return vec![kvid];
793            }
794
795            match decl.encoding {
796                KVarEncoding::Single => {
797                    let kvid = self.kvars.push(FixpointKVar::new(all_args, kvid));
798                    vec![kvid]
799                }
800                KVarEncoding::Conj => {
801                    let n = usize::max(decl.self_args, 1);
802                    (0..n)
803                        .map(|i| {
804                            let sorts = all_args[i..].to_vec();
805                            self.kvars.push(FixpointKVar::new(sorts, kvid))
806                        })
807                        .collect_vec()
808                }
809            }
810        })
811    }
812
813    fn into_fixpoint(self) -> Vec<fixpoint::KVarDecl> {
814        self.kvars
815            .into_iter_enumerated()
816            .map(|(kvid, kvar)| {
817                fixpoint::KVarDecl::new(kvid, kvar.sorts, format!("orig: {:?}", kvar.orig))
818            })
819            .collect()
820    }
821}
822
823/// Environment used to map from [`rty::Var`] to a [`fixpoint::LocalVar`].
824struct LocalVarEnv {
825    local_var_gen: IndexGen<fixpoint::LocalVar>,
826    fvars: UnordMap<rty::Name, fixpoint::LocalVar>,
827    /// Layers of late bound variables
828    layers: Vec<Vec<fixpoint::LocalVar>>,
829}
830
831impl LocalVarEnv {
832    fn new() -> Self {
833        Self { local_var_gen: IndexGen::new(), fvars: Default::default(), layers: Vec::new() }
834    }
835
836    // This doesn't require to be mutable because `IndexGen` uses atomics, but we make it mutable
837    // to better declare the intent.
838    fn fresh_name(&mut self) -> fixpoint::LocalVar {
839        self.local_var_gen.fresh()
840    }
841
842    fn insert_fvar_map(&mut self, name: rty::Name) -> fixpoint::LocalVar {
843        let fresh = self.fresh_name();
844        self.fvars.insert(name, fresh);
845        fresh
846    }
847
848    fn remove_fvar_map(&mut self, name: rty::Name) {
849        self.fvars.remove(&name);
850    }
851
852    /// Push a layer of bound variables assigning a fresh [`fixpoint::LocalVar`] to each one
853    fn push_layer_with_fresh_names(&mut self, count: usize) {
854        let layer = (0..count).map(|_| self.fresh_name()).collect();
855        self.layers.push(layer);
856    }
857
858    fn pop_layer(&mut self) -> Vec<fixpoint::LocalVar> {
859        self.layers.pop().unwrap()
860    }
861
862    fn get_fvar(&self, name: rty::Name) -> Option<fixpoint::LocalVar> {
863        self.fvars.get(&name).copied()
864    }
865
866    fn get_late_bvar(&self, debruijn: DebruijnIndex, var: BoundVar) -> Option<fixpoint::LocalVar> {
867        let depth = self.layers.len().checked_sub(debruijn.as_usize() + 1)?;
868        self.layers[depth].get(var.as_usize()).copied()
869    }
870}
871
872impl FixpointKVar {
873    fn new(sorts: Vec<fixpoint::Sort>, orig: rty::KVid) -> Self {
874        Self { sorts, orig }
875    }
876}
877
878pub struct KVarGen {
879    kvars: IndexVec<rty::KVid, KVarDecl>,
880    /// If true, generate dummy [holes] instead of kvars. Used during shape mode to avoid generating
881    /// unnecessary kvars.
882    ///
883    /// [holes]: rty::ExprKind::Hole
884    dummy: bool,
885}
886
887impl KVarGen {
888    pub(crate) fn new(dummy: bool) -> Self {
889        Self { kvars: IndexVec::new(), dummy }
890    }
891
892    fn get(&self, kvid: rty::KVid) -> &KVarDecl {
893        &self.kvars[kvid]
894    }
895
896    /// Generate a fresh [kvar] under several layers of [binders]. Each layer may contain any kind
897    /// of bound variable, but variables that are not of kind [`BoundVariableKind::Refine`] will
898    /// be filtered out.
899    ///
900    /// The variables bound in the last layer (last element of the `binders` slice) is expected to
901    /// have only [`BoundVariableKind::Refine`] and all its elements are used as the [self arguments].
902    /// The rest of the binders are appended to the `scope`.
903    ///
904    /// Note that the returned expression will have escaping variables and it is up to the caller to
905    /// put it under an appropriate number of binders.
906    ///
907    /// Prefer using [`InferCtxt::fresh_kvar`] when possible.
908    ///
909    /// [binders]: rty::Binder
910    /// [kvar]: rty::KVar
911    /// [`InferCtxt::fresh_kvar`]: crate::infer::InferCtxt::fresh_kvar
912    /// [self arguments]: rty::KVar::self_args
913    /// [`BoundVariableKind::Refine`]: rty::BoundVariableKind::Refine
914    pub fn fresh(
915        &mut self,
916        binders: &[rty::BoundVariableKinds],
917        scope: impl IntoIterator<Item = (rty::Var, rty::Sort)>,
918        encoding: KVarEncoding,
919    ) -> rty::Expr {
920        if self.dummy {
921            return rty::Expr::hole(rty::HoleKind::Pred);
922        }
923
924        let args = itertools::chain(
925            binders.iter().rev().enumerate().flat_map(|(level, vars)| {
926                let debruijn = DebruijnIndex::from_usize(level);
927                vars.iter()
928                    .cloned()
929                    .enumerate()
930                    .flat_map(move |(idx, var)| {
931                        if let rty::BoundVariableKind::Refine(sort, _, kind) = var {
932                            let br = rty::BoundReft { var: BoundVar::from_usize(idx), kind };
933                            Some((rty::Var::Bound(debruijn, br), sort))
934                        } else {
935                            None
936                        }
937                    })
938            }),
939            scope,
940        );
941        let [.., last] = binders else {
942            return self.fresh_inner(0, [], encoding);
943        };
944        let num_self_args = last
945            .iter()
946            .filter(|var| matches!(var, rty::BoundVariableKind::Refine(..)))
947            .count();
948        self.fresh_inner(num_self_args, args, encoding)
949    }
950
951    fn fresh_inner<A>(&mut self, self_args: usize, args: A, encoding: KVarEncoding) -> rty::Expr
952    where
953        A: IntoIterator<Item = (rty::Var, rty::Sort)>,
954    {
955        // asset last one has things
956        let mut sorts = vec![];
957        let mut exprs = vec![];
958
959        let mut flattened_self_args = 0;
960        for (i, (var, sort)) in args.into_iter().enumerate() {
961            let is_self_arg = i < self_args;
962            let var = var.to_expr();
963            sort.walk(|sort, proj| {
964                if !matches!(sort, rty::Sort::Loc) {
965                    flattened_self_args += is_self_arg as usize;
966                    sorts.push(sort.clone());
967                    exprs.push(rty::Expr::field_projs(&var, proj));
968                }
969            });
970        }
971
972        let kvid = self
973            .kvars
974            .push(KVarDecl { self_args: flattened_self_args, sorts, encoding });
975
976        let kvar = rty::KVar::new(kvid, flattened_self_args, exprs);
977        rty::Expr::kvar(kvar)
978    }
979}
980
981#[derive(Clone)]
982struct KVarDecl {
983    self_args: usize,
984    sorts: Vec<rty::Sort>,
985    encoding: KVarEncoding,
986}
987
988/// How an [`rty::KVar`] is encoded in the fixpoint constraint
989#[derive(Clone, Copy)]
990pub enum KVarEncoding {
991    /// Generate a single kvar appending the self arguments and the scope, i.e.,
992    /// a kvar `$k(a0, ...)[b0, ...]` becomes `$k(a0, ..., b0, ...)` in the fixpoint constraint.
993    Single,
994    /// Generate a conjunction of kvars, one per argument in [`rty::KVar::args`].
995    /// Concretely, a kvar `$k(a0, a1, ..., an)[b0, ...]` becomes
996    /// `$k0(a0, a1, ..., an, b0, ...) ∧ $k1(a1, ..., an, b0, ...) ∧ ... ∧ $kn(an, b0, ...)`
997    Conj,
998}
999
1000impl std::fmt::Display for TagIdx {
1001    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1002        write!(f, "{}", self.as_u32())
1003    }
1004}
1005
1006impl std::str::FromStr for TagIdx {
1007    type Err = std::num::ParseIntError;
1008
1009    fn from_str(s: &str) -> Result<Self, Self::Err> {
1010        Ok(Self::from_u32(s.parse()?))
1011    }
1012}
1013
1014struct ExprEncodingCtxt<'genv, 'tcx> {
1015    genv: GlobalEnv<'genv, 'tcx>,
1016    local_var_env: LocalVarEnv,
1017    global_var_gen: IndexGen<fixpoint::GlobalVar>,
1018    const_map: ConstMap<'tcx>,
1019    fun_def_map: FunDefMap,
1020    errors: Errors<'genv>,
1021    /// Id of the item being checked. This is a [`MaybeExternId`] because we may be encoding
1022    /// invariants for an extern spec on an enum.
1023    def_id: MaybeExternId,
1024    infcx: rustc_infer::infer::InferCtxt<'tcx>,
1025}
1026
1027impl<'genv, 'tcx> ExprEncodingCtxt<'genv, 'tcx> {
1028    fn new(genv: GlobalEnv<'genv, 'tcx>, def_id: MaybeExternId) -> Self {
1029        Self {
1030            genv,
1031            local_var_env: LocalVarEnv::new(),
1032            global_var_gen: IndexGen::new(),
1033            const_map: Default::default(),
1034            fun_def_map: Default::default(),
1035            errors: Errors::new(genv.sess()),
1036            def_id,
1037            infcx: genv
1038                .tcx()
1039                .infer_ctxt()
1040                .with_next_trait_solver(true)
1041                .build(TypingMode::non_body_analysis()),
1042        }
1043    }
1044
1045    fn def_span(&self) -> Span {
1046        self.genv.tcx().def_span(self.def_id)
1047    }
1048
1049    fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
1050        match var {
1051            rty::Var::Free(name) => {
1052                self.local_var_env
1053                    .get_fvar(*name)
1054                    .unwrap_or_else(|| {
1055                        span_bug!(self.def_span(), "no entry found for name: `{name:?}`")
1056                    })
1057                    .into()
1058            }
1059            rty::Var::Bound(debruijn, breft) => {
1060                self.local_var_env
1061                    .get_late_bvar(*debruijn, breft.var)
1062                    .unwrap_or_else(|| {
1063                        span_bug!(self.def_span(), "no entry found for late bound var: `{breft:?}`")
1064                    })
1065                    .into()
1066            }
1067            rty::Var::ConstGeneric(param) => fixpoint::Var::ConstGeneric(*param),
1068            rty::Var::EarlyParam(param) => fixpoint::Var::Param(*param),
1069            rty::Var::EVar(_) => {
1070                span_bug!(self.def_span(), "unexpected evar: `{var:?}`")
1071            }
1072        }
1073    }
1074
1075    fn variant_to_fixpoint(
1076        &self,
1077        scx: &mut SortEncodingCtxt,
1078        enum_def_id: &DefId,
1079        idx: VariantIdx,
1080    ) -> fixpoint::Var {
1081        let adt_id = scx.declare_adt(*enum_def_id);
1082        fixpoint::Var::DataCtor(adt_id, idx)
1083    }
1084
1085    fn struct_fields_to_fixpoint(
1086        &mut self,
1087        did: &DefId,
1088        flds: &[rty::Expr],
1089        scx: &mut SortEncodingCtxt,
1090    ) -> QueryResult<fixpoint::Expr> {
1091        // do not generate 1-tuples
1092        if let [fld] = flds {
1093            self.expr_to_fixpoint(fld, scx)
1094        } else {
1095            let adt_id = scx.declare_adt(*did);
1096            let ctor = fixpoint::Expr::Var(fixpoint::Var::DataCtor(adt_id, VariantIdx::ZERO));
1097            let args = flds
1098                .iter()
1099                .map(|fld| self.expr_to_fixpoint(fld, scx))
1100                .try_collect()?;
1101            Ok(fixpoint::Expr::App(Box::new(ctor), args))
1102        }
1103    }
1104
1105    fn fields_to_fixpoint(
1106        &mut self,
1107        flds: &[rty::Expr],
1108        scx: &mut SortEncodingCtxt,
1109    ) -> QueryResult<fixpoint::Expr> {
1110        // do not generate 1-tuples
1111        if let [fld] = flds {
1112            self.expr_to_fixpoint(fld, scx)
1113        } else {
1114            scx.declare_tuple(flds.len());
1115            let ctor = fixpoint::Expr::Var(fixpoint::Var::TupleCtor { arity: flds.len() });
1116            let args = flds
1117                .iter()
1118                .map(|fld| self.expr_to_fixpoint(fld, scx))
1119                .try_collect()?;
1120            Ok(fixpoint::Expr::App(Box::new(ctor), args))
1121        }
1122    }
1123
1124    fn internal_func_to_fixpoint(
1125        &mut self,
1126        internal_func: &InternalFuncKind,
1127        sort_args: &[rty::SortArg],
1128        args: &[rty::Expr],
1129        scx: &mut SortEncodingCtxt,
1130    ) -> QueryResult<fixpoint::Expr> {
1131        match internal_func {
1132            InternalFuncKind::Val(op) => {
1133                let func = fixpoint::Expr::Var(self.define_const_for_prim_op(op, scx));
1134                let args = self.exprs_to_fixpoint(args, scx)?;
1135                Ok(fixpoint::Expr::App(Box::new(func), args))
1136            }
1137            InternalFuncKind::Rel(op) => {
1138                let expr = if let Some(prim_rel) = self.genv.prim_rel_for(op)? {
1139                    prim_rel.body.replace_bound_refts(args)
1140                } else {
1141                    rty::Expr::tt()
1142                };
1143                self.expr_to_fixpoint(&expr, scx)
1144            }
1145            InternalFuncKind::Cast => {
1146                let [rty::SortArg::Sort(from), rty::SortArg::Sort(to)] = &sort_args else {
1147                    span_bug!(self.def_span(), "unexpected cast")
1148                };
1149                match from.cast_kind(to) {
1150                    rty::CastKind::Identity => self.expr_to_fixpoint(&args[0], scx),
1151                    rty::CastKind::BoolToInt => {
1152                        Ok(fixpoint::Expr::IfThenElse(Box::new([
1153                            self.expr_to_fixpoint(&args[0], scx)?,
1154                            fixpoint::Expr::int(1),
1155                            fixpoint::Expr::int(0),
1156                        ])))
1157                    }
1158                    rty::CastKind::IntoUnit => self.expr_to_fixpoint(&rty::Expr::unit(), scx),
1159                    rty::CastKind::Uninterpreted => {
1160                        let func = fixpoint::Expr::Var(self.define_const_for_cast(from, to, scx));
1161                        let args = self.exprs_to_fixpoint(args, scx)?;
1162                        Ok(fixpoint::Expr::App(Box::new(func), args))
1163                    }
1164                }
1165            }
1166        }
1167    }
1168
1169    fn structurally_normalize_expr(&self, expr: &rty::Expr) -> QueryResult<rty::Expr> {
1170        structurally_normalize_expr(self.genv, self.def_id.resolved_id(), &self.infcx, expr)
1171    }
1172
1173    fn expr_to_fixpoint(
1174        &mut self,
1175        expr: &rty::Expr,
1176        scx: &mut SortEncodingCtxt,
1177    ) -> QueryResult<fixpoint::Expr> {
1178        let expr = self.structurally_normalize_expr(expr)?;
1179        let e = match expr.kind() {
1180            rty::ExprKind::Var(var) => fixpoint::Expr::Var(self.var_to_fixpoint(var)),
1181            rty::ExprKind::Constant(c) => const_to_fixpoint(*c),
1182            rty::ExprKind::BinaryOp(op, e1, e2) => self.bin_op_to_fixpoint(op, e1, e2, scx)?,
1183            rty::ExprKind::UnaryOp(op, e) => self.un_op_to_fixpoint(*op, e, scx)?,
1184            rty::ExprKind::FieldProj(e, proj) => self.proj_to_fixpoint(e, *proj, scx)?,
1185            rty::ExprKind::Tuple(flds) => self.fields_to_fixpoint(flds, scx)?,
1186            rty::ExprKind::Ctor(rty::Ctor::Struct(did), flds) => {
1187                self.struct_fields_to_fixpoint(did, flds, scx)?
1188            }
1189            rty::ExprKind::IsCtor(def_id, variant_idx, e) => {
1190                let ctor = self.variant_to_fixpoint(scx, def_id, *variant_idx);
1191                let e = self.expr_to_fixpoint(e, scx)?;
1192                fixpoint::Expr::IsCtor(ctor, Box::new(e))
1193            }
1194            rty::ExprKind::Ctor(rty::Ctor::Enum(did, idx), _) => {
1195                fixpoint::Expr::Var(self.variant_to_fixpoint(scx, did, *idx))
1196            }
1197            rty::ExprKind::ConstDefId(did) => {
1198                let var = self.define_const_for_rust_const(*did, scx);
1199                fixpoint::Expr::Var(var)
1200            }
1201            rty::ExprKind::App(func, sort_args, args) => {
1202                if let rty::ExprKind::InternalFunc(func) = func.kind() {
1203                    self.internal_func_to_fixpoint(func, sort_args, args, scx)?
1204                } else {
1205                    let func = self.expr_to_fixpoint(func, scx)?;
1206                    let args = self.exprs_to_fixpoint(args, scx)?;
1207                    fixpoint::Expr::App(Box::new(func), args)
1208                }
1209            }
1210            rty::ExprKind::IfThenElse(p, e1, e2) => {
1211                fixpoint::Expr::IfThenElse(Box::new([
1212                    self.expr_to_fixpoint(p, scx)?,
1213                    self.expr_to_fixpoint(e1, scx)?,
1214                    self.expr_to_fixpoint(e2, scx)?,
1215                ]))
1216            }
1217            rty::ExprKind::Alias(alias_reft, args) => {
1218                let sort = self.genv.sort_of_assoc_reft(alias_reft.assoc_id)?;
1219                let sort = sort.instantiate_identity();
1220                let func =
1221                    fixpoint::Expr::Var(self.define_const_for_alias_reft(alias_reft, sort, scx));
1222                let args = args
1223                    .iter()
1224                    .map(|expr| self.expr_to_fixpoint(expr, scx))
1225                    .try_collect()?;
1226                fixpoint::Expr::App(Box::new(func), args)
1227            }
1228            rty::ExprKind::Abs(lam) => {
1229                let var = self.define_const_for_lambda(lam, scx);
1230                fixpoint::Expr::Var(var)
1231            }
1232            rty::ExprKind::Let(init, body) => {
1233                debug_assert_eq!(body.vars().len(), 1);
1234                let init = self.expr_to_fixpoint(init, scx)?;
1235
1236                self.local_var_env.push_layer_with_fresh_names(1);
1237                let body = self.expr_to_fixpoint(body.skip_binder_ref(), scx)?;
1238                let vars = self.local_var_env.pop_layer();
1239
1240                fixpoint::Expr::Let(vars[0].into(), Box::new([init, body]))
1241            }
1242            rty::ExprKind::GlobalFunc(SpecFuncKind::Thy(itf)) => fixpoint::Expr::ThyFunc(*itf),
1243            rty::ExprKind::GlobalFunc(SpecFuncKind::Uif(def_id)) => {
1244                fixpoint::Expr::Var(self.define_const_for_uif(*def_id, scx))
1245            }
1246            rty::ExprKind::GlobalFunc(SpecFuncKind::Def(def_id)) => {
1247                fixpoint::Expr::Var(self.declare_fun(*def_id))
1248            }
1249            rty::ExprKind::Hole(..)
1250            | rty::ExprKind::KVar(_)
1251            | rty::ExprKind::Local(_)
1252            | rty::ExprKind::PathProj(..)
1253            | rty::ExprKind::ForAll(_)
1254            | rty::ExprKind::InternalFunc(_) => {
1255                span_bug!(self.def_span(), "unexpected expr: `{expr:?}`")
1256            }
1257            rty::ExprKind::BoundedQuant(kind, rng, body) => {
1258                let exprs = (rng.start..rng.end).map(|i| {
1259                    let arg = rty::Expr::constant(rty::Constant::from(i));
1260                    body.replace_bound_reft(&arg)
1261                });
1262                let expr = match kind {
1263                    flux_middle::fhir::QuantKind::Forall => rty::Expr::and_from_iter(exprs),
1264                    flux_middle::fhir::QuantKind::Exists => rty::Expr::or_from_iter(exprs),
1265                };
1266                self.expr_to_fixpoint(&expr, scx)?
1267            }
1268        };
1269        Ok(e)
1270    }
1271
1272    fn exprs_to_fixpoint<'b>(
1273        &mut self,
1274        exprs: impl IntoIterator<Item = &'b rty::Expr>,
1275        scx: &mut SortEncodingCtxt,
1276    ) -> QueryResult<Vec<fixpoint::Expr>> {
1277        exprs
1278            .into_iter()
1279            .map(|e| self.expr_to_fixpoint(e, scx))
1280            .try_collect()
1281    }
1282
1283    fn proj_to_fixpoint(
1284        &mut self,
1285        e: &rty::Expr,
1286        proj: rty::FieldProj,
1287        scx: &mut SortEncodingCtxt,
1288    ) -> QueryResult<fixpoint::Expr> {
1289        let arity = proj.arity(self.genv)?;
1290        // we encode 1-tuples as the single element inside so no projection necessary here
1291        if arity == 1 {
1292            self.expr_to_fixpoint(e, scx)
1293        } else {
1294            let proj = match proj {
1295                rty::FieldProj::Tuple { arity, field } => {
1296                    scx.declare_tuple(arity);
1297                    fixpoint::Var::TupleProj { arity, field }
1298                }
1299                rty::FieldProj::Adt { def_id, field } => {
1300                    let adt_id = scx.declare_adt(def_id);
1301                    fixpoint::Var::DataProj { adt_id, field }
1302                }
1303            };
1304            let proj = fixpoint::Expr::Var(proj);
1305            Ok(fixpoint::Expr::App(Box::new(proj), vec![self.expr_to_fixpoint(e, scx)?]))
1306        }
1307    }
1308
1309    fn un_op_to_fixpoint(
1310        &mut self,
1311        op: rty::UnOp,
1312        e: &rty::Expr,
1313        scx: &mut SortEncodingCtxt,
1314    ) -> QueryResult<fixpoint::Expr> {
1315        match op {
1316            rty::UnOp::Not => Ok(fixpoint::Expr::Not(Box::new(self.expr_to_fixpoint(e, scx)?))),
1317            rty::UnOp::Neg => Ok(fixpoint::Expr::Neg(Box::new(self.expr_to_fixpoint(e, scx)?))),
1318        }
1319    }
1320
1321    fn bv_rel_to_fixpoint(&self, rel: &fixpoint::BinRel) -> fixpoint::Expr {
1322        let itf = match rel {
1323            fixpoint::BinRel::Gt => fixpoint::ThyFunc::BvUgt,
1324            fixpoint::BinRel::Ge => fixpoint::ThyFunc::BvUge,
1325            fixpoint::BinRel::Lt => fixpoint::ThyFunc::BvUlt,
1326            fixpoint::BinRel::Le => fixpoint::ThyFunc::BvUle,
1327            _ => span_bug!(self.def_span(), "not a bitvector relation!"),
1328        };
1329        fixpoint::Expr::ThyFunc(itf)
1330    }
1331
1332    fn bv_op_to_fixpoint(&self, op: &rty::BinOp) -> fixpoint::Expr {
1333        let itf = match op {
1334            rty::BinOp::Add(_) => fixpoint::ThyFunc::BvAdd,
1335            rty::BinOp::Sub(_) => fixpoint::ThyFunc::BvSub,
1336            rty::BinOp::Mul(_) => fixpoint::ThyFunc::BvMul,
1337            rty::BinOp::Div(_) => fixpoint::ThyFunc::BvUdiv,
1338            rty::BinOp::Mod(_) => fixpoint::ThyFunc::BvUrem,
1339            rty::BinOp::BitAnd => fixpoint::ThyFunc::BvAnd,
1340            rty::BinOp::BitOr => fixpoint::ThyFunc::BvOr,
1341            rty::BinOp::BitXor => fixpoint::ThyFunc::BvXor,
1342            rty::BinOp::BitShl => fixpoint::ThyFunc::BvShl,
1343            rty::BinOp::BitShr => fixpoint::ThyFunc::BvLshr,
1344            _ => span_bug!(self.def_span(), "not a bitvector operation!"),
1345        };
1346        fixpoint::Expr::ThyFunc(itf)
1347    }
1348
1349    fn bin_op_to_fixpoint(
1350        &mut self,
1351        op: &rty::BinOp,
1352        e1: &rty::Expr,
1353        e2: &rty::Expr,
1354        scx: &mut SortEncodingCtxt,
1355    ) -> QueryResult<fixpoint::Expr> {
1356        let op = match op {
1357            rty::BinOp::Eq => {
1358                return Ok(fixpoint::Expr::Atom(
1359                    fixpoint::BinRel::Eq,
1360                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1361                ));
1362            }
1363            rty::BinOp::Ne => {
1364                return Ok(fixpoint::Expr::Atom(
1365                    fixpoint::BinRel::Ne,
1366                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1367                ));
1368            }
1369            rty::BinOp::Gt(sort) => {
1370                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Gt, e1, e2, scx);
1371            }
1372            rty::BinOp::Ge(sort) => {
1373                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Ge, e1, e2, scx);
1374            }
1375            rty::BinOp::Lt(sort) => {
1376                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Lt, e1, e2, scx);
1377            }
1378            rty::BinOp::Le(sort) => {
1379                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Le, e1, e2, scx);
1380            }
1381            rty::BinOp::And => {
1382                return Ok(fixpoint::Expr::And(vec![
1383                    self.expr_to_fixpoint(e1, scx)?,
1384                    self.expr_to_fixpoint(e2, scx)?,
1385                ]));
1386            }
1387            rty::BinOp::Or => {
1388                return Ok(fixpoint::Expr::Or(vec![
1389                    self.expr_to_fixpoint(e1, scx)?,
1390                    self.expr_to_fixpoint(e2, scx)?,
1391                ]));
1392            }
1393            rty::BinOp::Imp => {
1394                return Ok(fixpoint::Expr::Imp(Box::new([
1395                    self.expr_to_fixpoint(e1, scx)?,
1396                    self.expr_to_fixpoint(e2, scx)?,
1397                ])));
1398            }
1399            rty::BinOp::Iff => {
1400                return Ok(fixpoint::Expr::Iff(Box::new([
1401                    self.expr_to_fixpoint(e1, scx)?,
1402                    self.expr_to_fixpoint(e2, scx)?,
1403                ])));
1404            }
1405            rty::BinOp::Add(rty::Sort::BitVec(_))
1406            | rty::BinOp::Sub(rty::Sort::BitVec(_))
1407            | rty::BinOp::Mul(rty::Sort::BitVec(_))
1408            | rty::BinOp::Div(rty::Sort::BitVec(_))
1409            | rty::BinOp::Mod(rty::Sort::BitVec(_))
1410            | rty::BinOp::BitAnd
1411            | rty::BinOp::BitOr
1412            | rty::BinOp::BitXor
1413            | rty::BinOp::BitShl
1414            | rty::BinOp::BitShr => {
1415                let bv_func = self.bv_op_to_fixpoint(op);
1416                return Ok(fixpoint::Expr::App(
1417                    Box::new(bv_func),
1418                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1419                ));
1420            }
1421            rty::BinOp::Add(_) => fixpoint::BinOp::Add,
1422            rty::BinOp::Sub(_) => fixpoint::BinOp::Sub,
1423            rty::BinOp::Mul(_) => fixpoint::BinOp::Mul,
1424            rty::BinOp::Div(_) => fixpoint::BinOp::Div,
1425            rty::BinOp::Mod(_) => fixpoint::BinOp::Mod,
1426        };
1427        Ok(fixpoint::Expr::BinaryOp(
1428            op,
1429            Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1430        ))
1431    }
1432
1433    /// A binary relation is encoded as a structurally recursive relation between aggregate sorts.
1434    /// For "leaf" expressions, we encode them as an interpreted relation if the sort supports it,
1435    /// otherwise we use an uninterpreted function. For example, consider the following relation
1436    /// between two tuples of sort `(int, int -> int)`
1437    /// ```text
1438    /// (0, λv. v + 1) <= (1, λv. v + 1)
1439    /// ```
1440    /// The encoding in fixpoint will be
1441    ///
1442    /// ```text
1443    /// 0 <= 1 && (le (λv. v + 1) (λv. v + 1))
1444    /// ```
1445    /// Where `<=` is the (interpreted) less than or equal relation between integers and `le` is
1446    /// an uninterpreted relation between ([the encoding] of) lambdas.
1447    ///
1448    /// [the encoding]: Self::define_const_for_lambda
1449    fn bin_rel_to_fixpoint(
1450        &mut self,
1451        sort: &rty::Sort,
1452        rel: fixpoint::BinRel,
1453        e1: &rty::Expr,
1454        e2: &rty::Expr,
1455        scx: &mut SortEncodingCtxt,
1456    ) -> QueryResult<fixpoint::Expr> {
1457        let e = match sort {
1458            rty::Sort::Int | rty::Sort::Real | rty::Sort::Char => {
1459                fixpoint::Expr::Atom(
1460                    rel,
1461                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1462                )
1463            }
1464            rty::Sort::BitVec(_) => {
1465                let e1 = self.expr_to_fixpoint(e1, scx)?;
1466                let e2 = self.expr_to_fixpoint(e2, scx)?;
1467                let rel = self.bv_rel_to_fixpoint(&rel);
1468                fixpoint::Expr::App(Box::new(rel), vec![e1, e2])
1469            }
1470            rty::Sort::Tuple(sorts) => {
1471                let arity = sorts.len();
1472                self.apply_bin_rel_rec(sorts, rel, e1, e2, scx, |field| {
1473                    rty::FieldProj::Tuple { arity, field }
1474                })?
1475            }
1476            rty::Sort::App(rty::SortCtor::Adt(sort_def), args)
1477                if let Some(variant) = sort_def.opt_struct_variant() =>
1478            {
1479                let def_id = sort_def.did();
1480                let sorts = variant.field_sorts(args);
1481                self.apply_bin_rel_rec(&sorts, rel, e1, e2, scx, |field| {
1482                    rty::FieldProj::Adt { def_id, field }
1483                })?
1484            }
1485            _ => {
1486                let rel = fixpoint::Expr::Var(fixpoint::Var::UIFRel(rel));
1487                fixpoint::Expr::App(
1488                    Box::new(rel),
1489                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1490                )
1491            }
1492        };
1493        Ok(e)
1494    }
1495
1496    /// Apply binary relation recursively over aggregate expressions
1497    fn apply_bin_rel_rec(
1498        &mut self,
1499        sorts: &[rty::Sort],
1500        rel: fixpoint::BinRel,
1501        e1: &rty::Expr,
1502        e2: &rty::Expr,
1503        scx: &mut SortEncodingCtxt,
1504        mk_proj: impl Fn(u32) -> rty::FieldProj,
1505    ) -> QueryResult<fixpoint::Expr> {
1506        Ok(fixpoint::Expr::and(
1507            sorts
1508                .iter()
1509                .enumerate()
1510                .map(|(idx, s)| {
1511                    let proj = mk_proj(idx as u32);
1512                    let e1 = e1.proj_and_reduce(proj);
1513                    let e2 = e2.proj_and_reduce(proj);
1514                    self.bin_rel_to_fixpoint(s, rel, &e1, &e2, scx)
1515                })
1516                .try_collect()?,
1517        ))
1518    }
1519
1520    fn imm(
1521        &mut self,
1522        arg: &rty::Expr,
1523        sort: &rty::Sort,
1524        scx: &mut SortEncodingCtxt,
1525        bindings: &mut Vec<fixpoint::Bind>,
1526    ) -> QueryResult<fixpoint::Var> {
1527        let arg = self.expr_to_fixpoint(arg, scx)?;
1528        // Check if it's a variable after encoding, in case the encoding produced a variable from a
1529        // non-variable.
1530        if let fixpoint::Expr::Var(var) = arg {
1531            Ok(var)
1532        } else {
1533            let fresh = self.local_var_env.fresh_name();
1534            let pred = fixpoint::Expr::eq(fixpoint::Expr::Var(fresh.into()), arg);
1535            bindings.push(fixpoint::Bind {
1536                name: fresh.into(),
1537                sort: scx.sort_to_fixpoint(sort),
1538                pred: fixpoint::Pred::Expr(pred),
1539            });
1540            Ok(fresh.into())
1541        }
1542    }
1543
1544    /// Declare that the `def_id` of a Flux function definition needs to be encoded and assigns
1545    /// a name to it if it hasn't yet been declared. The encoding of the function body happens
1546    /// in [`Self::define_funs`].
1547    fn declare_fun(&mut self, def_id: FluxDefId) -> fixpoint::Var {
1548        *self.fun_def_map.entry(def_id).or_insert_with(|| {
1549            let id = self.global_var_gen.fresh();
1550            fixpoint::Var::Global(id, Some(def_id.name()))
1551        })
1552    }
1553
1554    /// The logic below is a bit "duplicated" with the `[`prim_op_sort`] in sortck.rs;
1555    /// They are not exactly the same because this is on rty and the other one on fhir.
1556    /// We should make sure these two remain in sync.
1557    ///
1558    /// (NOTE:PrimOpSort) We are somewhat "overloading" the BinOps: as we are using them
1559    /// for (a) interpreted operations on bit vectors AND (b) uninterpreted functions on integers.
1560    /// So when Binop::BitShr (a) appears in a ExprKind::BinOp, it means bit vectors, but
1561    /// (b) inside ExprKind::InternalFunc it means int.
1562    fn prim_op_sort(op: &rty::BinOp, span: Span) -> rty::PolyFuncSort {
1563        match op {
1564            rty::BinOp::BitAnd
1565            | rty::BinOp::BitOr
1566            | rty::BinOp::BitXor
1567            | rty::BinOp::BitShl
1568            | rty::BinOp::BitShr => {
1569                let fsort =
1570                    rty::FuncSort::new(vec![rty::Sort::Int, rty::Sort::Int], rty::Sort::Int);
1571                rty::PolyFuncSort::new(List::empty(), fsort)
1572            }
1573            _ => span_bug!(span, "unexpected prim op: {op:?} in `prim_op_sort`"),
1574        }
1575    }
1576
1577    fn define_const_for_cast(
1578        &mut self,
1579        from: &rty::Sort,
1580        to: &rty::Sort,
1581        scx: &mut SortEncodingCtxt,
1582    ) -> fixpoint::Var {
1583        let key = ConstKey::Cast(from.clone(), to.clone());
1584        self.const_map
1585            .entry(key)
1586            .or_insert_with(|| {
1587                let fsort = rty::FuncSort::new(vec![from.clone()], to.clone());
1588                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
1589                let sort = scx.func_sort_to_fixpoint(&fsort);
1590                fixpoint::ConstDecl {
1591                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), None),
1592                    sort,
1593                    comment: Some(format!("cast uif: ({from:?}) -> {to:?}")),
1594                }
1595            })
1596            .name
1597    }
1598
1599    fn define_const_for_prim_op(
1600        &mut self,
1601        op: &rty::BinOp,
1602        scx: &mut SortEncodingCtxt,
1603    ) -> fixpoint::Var {
1604        let key = ConstKey::PrimOp(op.clone());
1605        let span = self.def_span();
1606        self.const_map
1607            .entry(key)
1608            .or_insert_with(|| {
1609                let sort = scx.func_sort_to_fixpoint(&Self::prim_op_sort(op, span));
1610                fixpoint::ConstDecl {
1611                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), None),
1612                    sort,
1613                    comment: Some(format!("prim op uif: {op:?}")),
1614                }
1615            })
1616            .name
1617    }
1618
1619    fn define_const_for_uif(
1620        &mut self,
1621        def_id: FluxDefId,
1622        scx: &mut SortEncodingCtxt,
1623    ) -> fixpoint::Var {
1624        let key = ConstKey::Uif(def_id);
1625        self.const_map
1626            .entry(key)
1627            .or_insert_with(|| {
1628                let sort = scx.func_sort_to_fixpoint(&self.genv.func_sort(def_id));
1629                fixpoint::ConstDecl {
1630                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), Some(def_id.name())),
1631                    sort,
1632                    comment: Some(format!("uif: {def_id:?}")),
1633                }
1634            })
1635            .name
1636    }
1637
1638    fn define_const_for_rust_const(
1639        &mut self,
1640        def_id: DefId,
1641        scx: &mut SortEncodingCtxt,
1642    ) -> fixpoint::Var {
1643        let key = ConstKey::RustConst(def_id);
1644        self.const_map
1645            .entry(key)
1646            .or_insert_with(|| {
1647                let sort = self.genv.sort_of_def_id(def_id).unwrap().unwrap();
1648                fixpoint::ConstDecl {
1649                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), None),
1650                    sort: scx.sort_to_fixpoint(&sort),
1651                    comment: Some(format!("rust const: {}", def_id_to_string(def_id))),
1652                }
1653            })
1654            .name
1655    }
1656
1657    /// returns the 'constant' UIF for Var used to represent the alias_pred, creating and adding it
1658    /// to the const_map if necessary
1659    fn define_const_for_alias_reft(
1660        &mut self,
1661        alias_reft: &rty::AliasReft,
1662        fsort: rty::FuncSort,
1663        scx: &mut SortEncodingCtxt,
1664    ) -> fixpoint::Var {
1665        let tcx = self.genv.tcx();
1666        let args = alias_reft
1667            .args
1668            .to_rustc(tcx)
1669            .truncate_to(tcx, tcx.generics_of(alias_reft.assoc_id.parent()));
1670        let key = ConstKey::Alias(alias_reft.assoc_id, args);
1671        self.const_map
1672            .entry(key)
1673            .or_insert_with(|| {
1674                let comment = Some(format!("alias reft: {alias_reft:?}"));
1675                let name = fixpoint::Var::Global(self.global_var_gen.fresh(), None);
1676                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
1677                let sort = scx.func_sort_to_fixpoint(&fsort);
1678                fixpoint::ConstDecl { name, comment, sort }
1679            })
1680            .name
1681    }
1682
1683    /// We encode lambdas with uninterpreted constant. Two syntactically equal lambdas will be encoded
1684    /// with the same constant.
1685    fn define_const_for_lambda(
1686        &mut self,
1687        lam: &rty::Lambda,
1688        scx: &mut SortEncodingCtxt,
1689    ) -> fixpoint::Var {
1690        let key = ConstKey::Lambda(lam.clone());
1691        self.const_map
1692            .entry(key)
1693            .or_insert_with(|| {
1694                let comment = Some(format!("lambda: {lam:?}"));
1695                let name = fixpoint::Var::Global(self.global_var_gen.fresh(), None);
1696                let sort = scx.func_sort_to_fixpoint(&lam.fsort().to_poly());
1697                fixpoint::ConstDecl { name, comment, sort }
1698            })
1699            .name
1700    }
1701
1702    fn assume_const_values(
1703        &mut self,
1704        mut constraint: fixpoint::Constraint,
1705        scx: &mut SortEncodingCtxt,
1706    ) -> QueryResult<fixpoint::Constraint> {
1707        // Encoding the value for a constant could in theory define more constants for which
1708        // we need to assume values, so we iterate until there are no more constants.
1709        let mut idx = 0;
1710        while let Some((key, const_)) = self.const_map.get_index(idx) {
1711            idx += 1;
1712
1713            let ConstKey::RustConst(def_id) = key else { continue };
1714            let info = self.genv.constant_info(def_id)?;
1715            match info {
1716                rty::ConstantInfo::Uninterpreted => {}
1717                rty::ConstantInfo::Interpreted(val, _) => {
1718                    let e1 = fixpoint::Expr::Var(const_.name);
1719                    let e2 = self.expr_to_fixpoint(&val, scx)?;
1720                    let pred = fixpoint::Pred::Expr(e1.eq(e2));
1721                    constraint = fixpoint::Constraint::ForAll(
1722                        fixpoint::Bind {
1723                            name: fixpoint::Var::Underscore,
1724                            sort: fixpoint::Sort::Int,
1725                            pred,
1726                        },
1727                        Box::new(constraint),
1728                    );
1729                }
1730            }
1731        }
1732        Ok(constraint)
1733    }
1734
1735    fn qualifiers_for(
1736        &mut self,
1737        def_id: LocalDefId,
1738        scx: &mut SortEncodingCtxt,
1739    ) -> QueryResult<Vec<fixpoint::Qualifier>> {
1740        self.genv
1741            .qualifiers_for(def_id)?
1742            .map(|qual| self.qualifier_to_fixpoint(qual, scx))
1743            .try_collect()
1744    }
1745
1746    fn define_funs(
1747        &mut self,
1748        def_id: MaybeExternId,
1749        scx: &mut SortEncodingCtxt,
1750    ) -> QueryResult<(Vec<fixpoint::FunDef>, Vec<fixpoint::ConstDecl>)> {
1751        let reveals: UnordSet<FluxDefId> = self
1752            .genv
1753            .reveals_for(def_id.local_id())
1754            .iter()
1755            .copied()
1756            .collect();
1757        let proven_externally = self.genv.proven_externally(def_id.local_id());
1758        let mut consts = vec![];
1759        let mut defs = vec![];
1760
1761        // We iterate until encoding the body of functions doesn't require any more functions
1762        // to be encoded.
1763        let mut idx = 0;
1764        while let Some((&did, &name)) = self.fun_def_map.get_index(idx) {
1765            idx += 1;
1766
1767            let comment = format!("flux def: {did:?}");
1768            let info = self.genv.normalized_info(did);
1769            let revealed = reveals.contains(&did);
1770            if info.hide && !revealed && !proven_externally {
1771                let sort = scx.func_sort_to_fixpoint(&self.genv.func_sort(did));
1772                consts.push(fixpoint::ConstDecl { name, sort, comment: Some(comment) });
1773            } else {
1774                let out = scx.sort_to_fixpoint(self.genv.func_sort(did).expect_mono().output());
1775                let (args, body) = self.body_to_fixpoint(&info.body, scx)?;
1776                let fun_def = fixpoint::FunDef { name, args, body, out, comment: Some(comment) };
1777                defs.push((info.rank, fun_def));
1778            };
1779        }
1780
1781        // we sort by rank so the definitions go out without any forward dependencies.
1782        let defs = defs
1783            .into_iter()
1784            .sorted_by_key(|(rank, _)| *rank)
1785            .map(|(_, def)| def)
1786            .collect();
1787
1788        Ok((defs, consts))
1789    }
1790
1791    fn body_to_fixpoint(
1792        &mut self,
1793        body: &rty::Binder<rty::Expr>,
1794        scx: &mut SortEncodingCtxt,
1795    ) -> QueryResult<(Vec<(fixpoint::Var, fixpoint::Sort)>, fixpoint::Expr)> {
1796        self.local_var_env
1797            .push_layer_with_fresh_names(body.vars().len());
1798
1799        let expr = self.expr_to_fixpoint(body.as_ref().skip_binder(), scx)?;
1800
1801        let args: Vec<(fixpoint::Var, fixpoint::Sort)> =
1802            iter::zip(self.local_var_env.pop_layer(), body.vars())
1803                .map(|(name, var)| (name.into(), scx.sort_to_fixpoint(var.expect_sort())))
1804                .collect();
1805
1806        Ok((args, expr))
1807    }
1808
1809    fn qualifier_to_fixpoint(
1810        &mut self,
1811        qualifier: &rty::Qualifier,
1812        scx: &mut SortEncodingCtxt,
1813    ) -> QueryResult<fixpoint::Qualifier> {
1814        let (args, body) = self.body_to_fixpoint(&qualifier.body, scx)?;
1815        let name = qualifier.def_id.name().to_string();
1816        Ok(fixpoint::Qualifier { name, args, body })
1817    }
1818}
1819
1820fn mk_implies(assumption: fixpoint::Pred, cstr: fixpoint::Constraint) -> fixpoint::Constraint {
1821    fixpoint::Constraint::ForAll(
1822        fixpoint::Bind {
1823            name: fixpoint::Var::Underscore,
1824            sort: fixpoint::Sort::Int,
1825            pred: assumption,
1826        },
1827        Box::new(cstr),
1828    )
1829}