flux_infer/
fixpoint_encoding.rs

1//! Encoding of the refinement tree into a fixpoint constraint.
2
3use std::{
4    collections::{HashMap, HashSet},
5    hash::Hash,
6    iter,
7    ops::Range,
8};
9
10use fixpoint::{AdtId, OpaqueId};
11use flux_common::{
12    bug,
13    cache::QueryCache,
14    dbg,
15    index::{IndexGen, IndexVec},
16    span_bug, tracked_span_bug,
17};
18use flux_config::{self as config};
19use flux_errors::Errors;
20use flux_macros::DebugAsJson;
21use flux_middle::{
22    FixpointQueryKind,
23    def_id::{FluxDefId, MaybeExternId},
24    def_id_to_string,
25    global_env::GlobalEnv,
26    metrics::{self, Metric, TimingKind},
27    pretty::{NestedString, PrettyCx, PrettyNested},
28    queries::QueryResult,
29    query_bug,
30    rty::{
31        self, ESpan, EarlyReftParam, GenericArgsExt, InternalFuncKind, Lambda, List,
32        NameProvenance, PrettyMap, PrettyVar, SpecFuncKind, VariantIdx, fold::TypeFoldable as _,
33    },
34};
35use itertools::Itertools;
36use liquid_fixpoint::{
37    FixpointStatus, KVarBind, SmtSolver, VerificationResult,
38    parser::{FromSexp, ParseError},
39    sexp::Parser,
40};
41use rustc_data_structures::{
42    fx::{FxIndexMap, FxIndexSet},
43    unord::{UnordMap, UnordSet},
44};
45use rustc_hir::def_id::{DefId, LocalDefId};
46use rustc_index::newtype_index;
47use rustc_infer::infer::TyCtxtInferExt as _;
48use rustc_middle::ty::TypingMode;
49use rustc_span::{DUMMY_SP, Span, Symbol};
50use rustc_type_ir::{BoundVar, DebruijnIndex};
51use serde::{Deserialize, Deserializer, Serialize};
52
53use crate::{
54    fixpoint_encoding::fixpoint::FixpointTypes, fixpoint_qualifiers::FIXPOINT_QUALIFIERS,
55    lean_encoding::LeanEncoder, projections::structurally_normalize_expr,
56};
57
58pub mod decoding;
59
60pub mod fixpoint {
61    use std::fmt;
62
63    use flux_middle::{def_id::FluxDefId, rty::EarlyReftParam};
64    use liquid_fixpoint::{FixpointFmt, Identifier};
65    use rustc_abi::VariantIdx;
66    use rustc_hir::def_id::DefId;
67    use rustc_index::newtype_index;
68    use rustc_middle::ty::ParamConst;
69    use rustc_span::Symbol;
70
71    newtype_index! {
72        #[orderable]
73        pub struct KVid {}
74    }
75
76    impl Identifier for KVid {
77        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78            write!(f, "k{}", self.as_u32())
79        }
80    }
81
82    newtype_index! {
83        pub struct LocalVar {}
84    }
85
86    newtype_index! {
87        pub struct GlobalVar {}
88    }
89
90    newtype_index! {
91        /// Unique id assigned to each [`rty::AdtSortDef`] that needs to be encoded
92        /// into fixpoint
93        pub struct AdtId {}
94    }
95
96    newtype_index! {
97        /// Unique id assigned to each opaque/user sort that needs to be encoded
98        /// into fixpoint (see `DataSort::User(OpaqueId)` and `declare_opaque_sort`)
99        pub struct OpaqueId {}
100    }
101
102    #[derive(Hash, Copy, Clone, Debug, PartialEq, Eq)]
103    pub enum Var {
104        Underscore,
105        Global(GlobalVar, FluxDefId),
106        Const(GlobalVar, Option<DefId>),
107        Local(LocalVar),
108        DataCtor(AdtId, VariantIdx),
109        TupleCtor { arity: usize },
110        TupleProj { arity: usize, field: u32 },
111        DataProj { adt_id: AdtId, /* variant_idx: VariantIdx, */ field: u32 },
112        UIFRel(BinRel),
113        Param(EarlyReftParam),
114        ConstGeneric(ParamConst),
115    }
116
117    impl From<GlobalVar> for Var {
118        fn from(v: GlobalVar) -> Self {
119            Self::Const(v, None)
120        }
121    }
122
123    impl From<LocalVar> for Var {
124        fn from(v: LocalVar) -> Self {
125            Self::Local(v)
126        }
127    }
128
129    impl Identifier for Var {
130        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131            match self {
132                Var::Global(v, did) => write!(f, "f${}${}", did.name(), v.as_u32()),
133                Var::Const(v, _) => write!(f, "c{}", v.as_u32()),
134                Var::Local(v) => write!(f, "a{}", v.as_u32()),
135                Var::DataCtor(adt_id, variant_idx) => {
136                    write!(f, "mkadt{}${}", adt_id.as_u32(), variant_idx.as_u32())
137                }
138                Var::TupleCtor { arity } => write!(f, "mktuple{arity}"),
139                Var::TupleProj { arity, field } => write!(f, "tuple{arity}${field}"),
140                Var::DataProj { adt_id, field } => write!(f, "fld{}${field}", adt_id.as_u32()),
141                Var::UIFRel(BinRel::Gt) => write!(f, "gt"),
142                Var::UIFRel(BinRel::Ge) => write!(f, "ge"),
143                Var::UIFRel(BinRel::Lt) => write!(f, "lt"),
144                Var::UIFRel(BinRel::Le) => write!(f, "le"),
145                // these are actually not necessary because equality is interpreted for all sorts
146                Var::UIFRel(BinRel::Eq) => write!(f, "eq"),
147                Var::UIFRel(BinRel::Ne) => write!(f, "ne"),
148                Var::Underscore => write!(f, "_$"), // To avoid clashing with `_` used for `app (_ bv_op n)` for parametric SMT ops
149                Var::ConstGeneric(param) => {
150                    write!(f, "constgen${}${}", param.name, param.index)
151                }
152                Var::Param(param) => {
153                    write!(f, "reftgen${}${}", param.name, param.index)
154                }
155            }
156        }
157    }
158
159    #[derive(Clone, Hash, Debug, PartialEq, Eq)]
160    pub enum DataSort {
161        Tuple(usize),
162        Adt(AdtId),
163        User(OpaqueId),
164    }
165
166    impl Identifier for DataSort {
167        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168            match self {
169                DataSort::Tuple(arity) => {
170                    write!(f, "Tuple{arity}")
171                }
172                DataSort::Adt(adt_id) => {
173                    write!(f, "Adt{}", adt_id.as_u32())
174                }
175                DataSort::User(opaque_id) => {
176                    write!(f, "OpaqueAdt{}", opaque_id.as_u32())
177                }
178            }
179        }
180    }
181
182    #[derive(Hash, Clone, Debug)]
183    pub struct SymStr(pub Symbol);
184
185    #[cfg(feature = "rust-fixpoint")]
186    impl FixpointFmt for SymStr {
187        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188            write!(f, "{}", self.0)
189        }
190    }
191
192    #[cfg(not(feature = "rust-fixpoint"))]
193    impl FixpointFmt for SymStr {
194        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195            write!(f, "\"{}\"", self.0)
196        }
197    }
198
199    liquid_fixpoint::declare_types! {
200        type Sort = DataSort;
201        type KVar = KVid;
202        type Var = Var;
203        type String = SymStr;
204        type Tag = super::TagIdx;
205    }
206    pub use fixpoint_generated::*;
207}
208
209/// A type for rust constants that have a concrete interpretation
210pub type InterpretedConst = (fixpoint::ConstDecl, fixpoint::Expr);
211
212/// A type to represent Solutions for KVars
213pub type Solution = FxIndexMap<rty::KVid, rty::Binder<rty::Expr>>;
214pub type FixpointSolution = (Vec<(fixpoint::Var, fixpoint::Sort)>, fixpoint::Expr);
215pub type ClosedSolution = (Vec<(fixpoint::Var, fixpoint::Sort)>, FixpointSolution);
216
217/// A very explicit representation of [`Solution`] for debugging/tracing/serialization ONLY.
218#[derive(Serialize, DebugAsJson)]
219pub struct SolutionTrace(Vec<KvarSolutionTrace>);
220
221#[derive(Serialize, DebugAsJson)]
222pub struct KvarSolutionTrace {
223    pub name: String,
224    pub args: Vec<String>,
225    pub body: NestedString,
226}
227
228impl KvarSolutionTrace {
229    pub fn new(cx: &PrettyCx, kvid: rty::KVid, bind_expr: &rty::Binder<rty::Expr>) -> Self {
230        let mut args = Vec::new();
231        let body = cx
232            .nested_with_bound_vars("", bind_expr.vars(), Some("".to_string()), |prefix| {
233                for arg in prefix.split(',').map(|s| s.trim().to_string()) {
234                    args.push(arg);
235                }
236                bind_expr.skip_binder_ref().fmt_nested(cx)
237            })
238            .unwrap();
239
240        KvarSolutionTrace { name: format!("{kvid:?}"), args, body }
241    }
242}
243
244impl SolutionTrace {
245    pub fn new<T>(genv: GlobalEnv, ans: &Answer<T>) -> Self {
246        let cx = &PrettyCx::default(genv);
247        let res = ans
248            .solutions()
249            .map(|(kvid, bind_expr)| KvarSolutionTrace::new(cx, *kvid, bind_expr))
250            .collect();
251        SolutionTrace(res)
252    }
253}
254
255pub struct ParsedResult {
256    pub status: FixpointStatus<TagIdx>,
257    pub solution: FxIndexMap<fixpoint::KVid, FixpointSolution>,
258    pub non_cut_solution: FxIndexMap<fixpoint::KVid, FixpointSolution>,
259}
260
261#[derive(Debug, Clone, Default)]
262pub struct Answer<Tag> {
263    pub errors: Vec<Tag>,
264    pub cut_solution: Solution,
265    pub non_cut_solution: Solution,
266}
267
268impl<Tag> Answer<Tag> {
269    pub fn trivial() -> Self {
270        Self {
271            errors: Vec::new(),
272            cut_solution: FxIndexMap::default(),
273            non_cut_solution: FxIndexMap::default(),
274        }
275    }
276
277    pub fn solutions(&self) -> impl Iterator<Item = (&rty::KVid, &rty::Binder<rty::Expr>)> {
278        self.cut_solution.iter().chain(self.non_cut_solution.iter())
279    }
280}
281
282newtype_index! {
283    #[debug_format = "TagIdx({})"]
284    pub struct TagIdx {}
285}
286
287impl Serialize for TagIdx {
288    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
289        self.as_u32().serialize(serializer)
290    }
291}
292
293impl<'de> Deserialize<'de> for TagIdx {
294    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
295        let idx = usize::deserialize(deserializer)?;
296        Ok(TagIdx::from_u32(idx as u32))
297    }
298}
299
300/// Keep track of all the data sorts that we need to define in fixpoint to encode the constraint.
301#[derive(Default)]
302pub struct SortEncodingCtxt {
303    /// Set of all the tuple arities that need to be defined
304    tuples: UnordSet<usize>,
305    /// Set of all the [`AdtDefSortDef`](rty::AdtSortDef) that need to be declared as
306    /// Fixpoint data-decls
307    adt_sorts: FxIndexSet<DefId>,
308    /// Set of all opaque types that need to be defined
309    opaque_sorts: FxIndexSet<FluxDefId>,
310}
311
312impl SortEncodingCtxt {
313    pub fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
314        match sort {
315            rty::Sort::Int => fixpoint::Sort::Int,
316            rty::Sort::Real => fixpoint::Sort::Real,
317            rty::Sort::Bool => fixpoint::Sort::Bool,
318            rty::Sort::Str => fixpoint::Sort::Str,
319            rty::Sort::Char => fixpoint::Sort::Int,
320            rty::Sort::BitVec(size) => fixpoint::Sort::BitVec(Box::new(bv_size_to_fixpoint(*size))),
321
322            // We encode type parameter sorts and (unormalizable) type alias sorts as integers.
323            // Well-formedness should ensure values of these sorts are used "opaquely", i.e.
324            // the only values of these sorts are variables.
325            rty::Sort::Param(_)
326            | rty::Sort::RawPtr
327            | rty::Sort::Alias(rty::AliasKind::Opaque | rty::AliasKind::Projection, ..) => {
328                fixpoint::Sort::Int
329            }
330            rty::Sort::App(rty::SortCtor::Set, args) => {
331                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
332                fixpoint::Sort::App(fixpoint::SortCtor::Set, args)
333            }
334            rty::Sort::App(rty::SortCtor::Map, args) => {
335                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
336                fixpoint::Sort::App(fixpoint::SortCtor::Map, args)
337            }
338            rty::Sort::App(rty::SortCtor::Adt(sort_def), args) => {
339                if let Some(variant) = sort_def.opt_struct_variant() {
340                    let sorts = variant.field_sorts(args);
341                    // do not generate 1-tuples
342                    if let [sort] = &sorts[..] {
343                        self.sort_to_fixpoint(sort)
344                    } else {
345                        let adt_id = self.declare_adt(sort_def.did());
346                        let ctor = fixpoint::SortCtor::Data(fixpoint::DataSort::Adt(adt_id));
347                        let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
348                        fixpoint::Sort::App(ctor, args)
349                    }
350                } else {
351                    debug_assert!(args.is_empty());
352                    let adt_id = self.declare_adt(sort_def.did());
353                    fixpoint::Sort::App(
354                        fixpoint::SortCtor::Data(fixpoint::DataSort::Adt(adt_id)),
355                        vec![],
356                    )
357                }
358            }
359            rty::Sort::App(rty::SortCtor::User(def_id), args) => {
360                let opaque_id = self.declare_opaque_sort(*def_id);
361                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
362                fixpoint::Sort::App(
363                    fixpoint::SortCtor::Data(fixpoint::DataSort::User(opaque_id)),
364                    args,
365                )
366            }
367            rty::Sort::Tuple(sorts) => {
368                // do not generate 1-tuples
369                if let [sort] = &sorts[..] {
370                    self.sort_to_fixpoint(sort)
371                } else {
372                    self.declare_tuple(sorts.len());
373                    let ctor = fixpoint::SortCtor::Data(fixpoint::DataSort::Tuple(sorts.len()));
374                    let args = sorts.iter().map(|s| self.sort_to_fixpoint(s)).collect();
375                    fixpoint::Sort::App(ctor, args)
376                }
377            }
378            rty::Sort::Func(sort) => self.func_sort_to_fixpoint(sort).into_sort(),
379            rty::Sort::Var(k) => fixpoint::Sort::Var(k.index()),
380            rty::Sort::Err
381            | rty::Sort::Infer(_)
382            | rty::Sort::Loc
383            | rty::Sort::Alias(rty::AliasKind::Free, _) => {
384                tracked_span_bug!("unexpected sort `{sort:?}`")
385            }
386        }
387    }
388
389    fn func_sort_to_fixpoint(&mut self, fsort: &rty::PolyFuncSort) -> fixpoint::FunSort {
390        let params = fsort.params().len();
391        let fsort = fsort.skip_binders();
392        let output = self.sort_to_fixpoint(fsort.output());
393        fixpoint::FunSort {
394            params,
395            inputs: fsort
396                .inputs()
397                .iter()
398                .map(|s| self.sort_to_fixpoint(s))
399                .collect(),
400            output,
401        }
402    }
403
404    fn declare_tuple(&mut self, arity: usize) {
405        self.tuples.insert(arity);
406    }
407
408    pub fn declare_opaque_sort(&mut self, def_id: FluxDefId) -> OpaqueId {
409        if let Some(idx) = self.opaque_sorts.get_index_of(&def_id) {
410            OpaqueId::from_usize(idx)
411        } else {
412            let opaque_id = OpaqueId::from_usize(self.opaque_sorts.len());
413            self.opaque_sorts.insert(def_id);
414            opaque_id
415        }
416    }
417
418    pub fn declare_adt(&mut self, did: DefId) -> AdtId {
419        if let Some(idx) = self.adt_sorts.get_index_of(&did) {
420            AdtId::from_usize(idx)
421        } else {
422            let adt_id = AdtId::from_usize(self.adt_sorts.len());
423            self.adt_sorts.insert(did);
424            adt_id
425        }
426    }
427
428    pub fn opaque_sorts_to_fixpoint(
429        &self,
430        genv: GlobalEnv,
431    ) -> Vec<(FluxDefId, fixpoint::SortDecl)> {
432        self.opaque_sorts
433            .iter()
434            .enumerate()
435            .map(|(idx, sort)| {
436                let param_count = genv.sort_decl_param_count(sort);
437                let sort_decl = fixpoint::SortDecl {
438                    name: fixpoint::DataSort::User(OpaqueId::from_usize(idx)),
439                    vars: param_count,
440                };
441                (*sort, sort_decl)
442            })
443            .collect()
444    }
445
446    fn append_adt_decls(
447        &mut self,
448        genv: GlobalEnv,
449        decls: &mut Vec<fixpoint::DataDecl>,
450    ) -> QueryResult {
451        // We iterate until we have processed all adt sorts because processing one adt sort may
452        // discover new adt sorts to process (e.g., if an adt field has an adt sort).
453        let mut idx = 0;
454        while let Some(adt_def_id) = self.adt_sorts.get_index(idx) {
455            let adt_id = AdtId::from_usize(idx);
456            let adt_sort_def = genv.adt_sort_def_of(adt_def_id)?;
457            decls.push(fixpoint::DataDecl {
458                name: fixpoint::DataSort::Adt(adt_id),
459                vars: adt_sort_def.param_count(),
460                ctors: adt_sort_def
461                    .variants()
462                    .iter_enumerated()
463                    .map(|(idx, variant)| {
464                        let name = fixpoint::Var::DataCtor(adt_id, idx);
465                        let fields = variant
466                            .field_sorts_instantiate_identity()
467                            .iter()
468                            .enumerate()
469                            .map(|(i, sort)| {
470                                fixpoint::DataField {
471                                    name: fixpoint::Var::DataProj { adt_id, field: i as u32 },
472                                    sort: self.sort_to_fixpoint(sort),
473                                }
474                            })
475                            .collect_vec();
476                        fixpoint::DataCtor { name, fields }
477                    })
478                    .collect(),
479            });
480            idx += 1;
481        }
482        Ok(())
483    }
484
485    fn append_tuple_decls(tuples: &UnordSet<usize>, decls: &mut Vec<fixpoint::DataDecl>) {
486        decls.extend(
487            tuples
488                .items()
489                .into_sorted_stable_ord()
490                .into_iter()
491                .map(|arity| {
492                    fixpoint::DataDecl {
493                        name: fixpoint::DataSort::Tuple(*arity),
494                        vars: *arity,
495                        ctors: vec![fixpoint::DataCtor {
496                            name: fixpoint::Var::TupleCtor { arity: *arity },
497                            fields: (0..(*arity as u32))
498                                .map(|field| {
499                                    fixpoint::DataField {
500                                        name: fixpoint::Var::TupleProj { arity: *arity, field },
501                                        sort: fixpoint::Sort::Var(field as usize),
502                                    }
503                                })
504                                .collect(),
505                        }],
506                    }
507                }),
508        );
509    }
510
511    pub fn encode_data_decls(&mut self, genv: GlobalEnv) -> QueryResult<Vec<fixpoint::DataDecl>> {
512        let mut decls = vec![];
513        self.append_adt_decls(genv, &mut decls)?;
514        Self::append_tuple_decls(&self.tuples, &mut decls);
515        Ok(decls)
516    }
517}
518
519fn bv_size_to_fixpoint(size: rty::BvSize) -> fixpoint::Sort {
520    match size {
521        rty::BvSize::Fixed(size) => fixpoint::Sort::BvSize(size),
522        rty::BvSize::Param(_var) => {
523            // I think we could encode the size as a sort variable, but this would require some care
524            // because smtlib doesn't really support parametric sizes. Fixpoint is probably already
525            // too liberal about this and it'd be easy to make it crash.
526            // fixpoint::Sort::Var(var.index)
527            bug!("unexpected parametric bit-vector size")
528        }
529        rty::BvSize::Infer(_) => bug!("unexpected infer variable for bit-vector size"),
530    }
531}
532
533pub type FunDeclMap = FxIndexMap<FluxDefId, fixpoint::Var>;
534type ConstMap<'tcx> = FxIndexMap<ConstKey<'tcx>, fixpoint::ConstDecl>;
535
536#[derive(Eq, Hash, PartialEq, Clone)]
537enum ConstKey<'tcx> {
538    RustConst(DefId),
539    Alias(FluxDefId, rustc_middle::ty::GenericArgsRef<'tcx>),
540    Lambda(Lambda),
541    PrimOp(rty::BinOp),
542    Cast(rty::Sort, rty::Sort),
543    PtrSize,
544}
545
546#[derive(Clone)]
547pub enum Backend {
548    Fixpoint,
549    Lean,
550}
551
552pub struct FixpointCtxt<'genv, 'tcx, T: Eq + Hash> {
553    comments: Vec<String>,
554    genv: GlobalEnv<'genv, 'tcx>,
555    kvars: KVarGen,
556    scx: SortEncodingCtxt,
557    kcx: KVarEncodingCtxt,
558    ecx: ExprEncodingCtxt<'genv, 'tcx>,
559    tags: IndexVec<TagIdx, T>,
560    tags_inv: UnordMap<T, TagIdx>,
561}
562
563pub type FixQueryCache = QueryCache<VerificationResult<TagIdx>>;
564
565pub use liquid_fixpoint::LeanStatus;
566
567/// Returns the cache key used for a function-body lean query.
568pub fn lean_task_key(tcx: rustc_middle::ty::TyCtxt, def_id: DefId) -> String {
569    FixpointQueryKind::Body.task_key(tcx, def_id)
570}
571
572impl<'genv, 'tcx, Tag> FixpointCtxt<'genv, 'tcx, Tag>
573where
574    Tag: std::hash::Hash + Eq + Copy,
575{
576    pub fn new(
577        genv: GlobalEnv<'genv, 'tcx>,
578        def_id: MaybeExternId,
579        kvars: KVarGen,
580        backend: Backend,
581    ) -> Self {
582        Self {
583            comments: vec![],
584            kvars,
585            scx: SortEncodingCtxt::default(),
586            genv,
587            ecx: ExprEncodingCtxt::new(genv, Some(def_id), backend),
588            kcx: Default::default(),
589            tags: IndexVec::new(),
590            tags_inv: Default::default(),
591        }
592    }
593
594    pub(crate) fn create_task(
595        &mut self,
596        def_id: MaybeExternId,
597        constraint: fixpoint::Constraint,
598        scrape_quals: bool,
599        solver: SmtSolver,
600    ) -> QueryResult<fixpoint::Task> {
601        let kvars = self.kcx.encode_kvars(&self.kvars, &mut self.scx);
602
603        let qualifiers = self
604            .ecx
605            .qualifiers_for(def_id.local_id(), &mut self.scx)?
606            .into_iter()
607            .chain(FIXPOINT_QUALIFIERS.iter().cloned())
608            .collect();
609
610        // Assuming values should happen after all encoding is done so we are sure we've collected
611        // all constants.
612        let constraint = self.ecx.assume_const_values(constraint, &mut self.scx)?;
613
614        let constants = self.ecx.const_env.const_map.values().cloned().collect_vec();
615
616        // Encode function bodies after qualifiers/assumptions so any functions referenced there
617        // are picked up as dependencies.
618        let define_funs = self.ecx.define_funs(def_id, &mut self.scx)?;
619
620        // The rust fixpoint implementation does not yet support polymorphic functions.
621        // For now we avoid including these by default so that cases where they are not needed can work.
622        // Should be removed when support is added.
623        #[cfg(not(feature = "rust-fixpoint"))]
624        let constants = if matches!(self.ecx.backend, Backend::Fixpoint) {
625            constants
626                .into_iter()
627                .chain(fixpoint::BinRel::INEQUALITIES.into_iter().map(|rel| {
628                    // ∀a. a -> a -> bool
629                    let sort = fixpoint::Sort::mk_func(
630                        1,
631                        [fixpoint::Sort::Var(0), fixpoint::Sort::Var(0)],
632                        fixpoint::Sort::Bool,
633                    );
634                    fixpoint::ConstDecl { name: fixpoint::Var::UIFRel(rel), sort, comment: None }
635                }))
636                .collect()
637        } else {
638            constants
639        };
640
641        // We are done encoding expressions. Check if there are any errors.
642        self.ecx.errors.to_result()?;
643
644        let task = fixpoint::Task {
645            comments: self.comments.clone(),
646            constants,
647            kvars,
648            define_funs,
649            constraint,
650            qualifiers,
651            scrape_quals,
652            solver,
653            data_decls: self.scx.encode_data_decls(self.genv)?,
654        };
655
656        if config::dump_constraint() {
657            dbg::dump_item_info(self.genv.tcx(), def_id.resolved_id(), "smt2", &task).unwrap();
658        }
659
660        Ok(task)
661    }
662
663    pub(crate) fn run_task(
664        &mut self,
665        cache: &mut FixQueryCache,
666        def_id: MaybeExternId,
667        kind: FixpointQueryKind,
668        task: &fixpoint::Task,
669    ) -> QueryResult<ParsedResult> {
670        let result = Self::run_task_with_cache(self.genv, task, def_id.resolved_id(), kind, cache);
671
672        if config::dump_checker_trace_info()
673            || self.genv.proven_externally(def_id.local_id()).is_some()
674        {
675            Ok(ParsedResult {
676                status: result.status,
677                solution: self.parse_kvar_solutions(&result.solution),
678                non_cut_solution: self.parse_kvar_solutions(&result.non_cuts_solution),
679            })
680        } else {
681            Ok(ParsedResult {
682                status: result.status,
683                solution: FxIndexMap::default(),
684                non_cut_solution: FxIndexMap::default(),
685            })
686        }
687    }
688
689    pub(crate) fn result_to_answer(&mut self, result: ParsedResult) -> Answer<Tag> {
690        let def_span = self.ecx.def_span();
691        let errors = match result.status {
692            FixpointStatus::Safe(_) => vec![],
693            FixpointStatus::Unsafe(_, errors) => {
694                metrics::incr_metric(Metric::CsError, errors.len() as u32);
695                errors
696                    .into_iter()
697                    .map(|err| self.tags[err.tag])
698                    .unique()
699                    .collect_vec()
700            }
701            FixpointStatus::Crash(err) => span_bug!(def_span, "fixpoint crash: {err:?}"),
702        };
703
704        let cut_solution = result
705            .solution
706            .into_iter()
707            .map(|(kvid, sol)| (kvid, self.fixpoint_to_solution(&sol)))
708            .collect_vec();
709
710        let non_cut_solution = result
711            .non_cut_solution
712            .into_iter()
713            .map(|(kvid, sol)| (kvid, self.fixpoint_to_solution(&sol)))
714            .collect_vec();
715
716        Answer {
717            errors,
718            cut_solution: self.kcx.group_kvar_solution(cut_solution),
719            non_cut_solution: self.kcx.group_kvar_solution(non_cut_solution),
720        }
721    }
722
723    fn parse_kvar_solutions(
724        &mut self,
725        kvar_binds: &[KVarBind],
726    ) -> FxIndexMap<fixpoint::KVid, FixpointSolution> {
727        kvar_binds
728            .iter()
729            .map(|b| (parse_kvid(&b.kvar), self.parse_kvar_solution(&b.val)))
730            .collect()
731    }
732
733    fn parse_kvar_solution(&mut self, expr: &str) -> FixpointSolution {
734        // 1. convert str -> sexp
735        let mut sexp_parser = Parser::new(expr);
736        let sexp = match sexp_parser.parse() {
737            Ok(sexp) => sexp,
738            Err(err) => {
739                tracked_span_bug!("cannot parse sexp: {expr:?}: {err:?}");
740            }
741        };
742        let mut fun_decl_map = HashMap::new();
743        for (def_id, var) in &self.ecx.const_env.fun_decl_map {
744            let fixpoint::Var::Global(idx, _) = var else {
745                bug!("non global var encountered for function")
746            };
747            fun_decl_map.insert(idx.index(), *def_id);
748        }
749        let mut const_decl_map = HashMap::new();
750        for (gvar, key) in &self.ecx.const_env.const_map_rev {
751            if let ConstKey::RustConst(def_id) = key {
752                const_decl_map.insert(gvar.as_usize(), *def_id);
753            }
754        }
755        // 2. convert sexp -> (binds, Expr<fixpoint_encoding::Types>)
756        let mut sexp_ctx =
757            SexpParseCtxt::new(&mut self.ecx.local_var_env, &fun_decl_map, &const_decl_map)
758                .into_wrapper();
759        sexp_ctx.parse_solution(&sexp).unwrap_or_else(|err| {
760            tracked_span_bug!("failed to parse solution sexp {sexp:?}: {err:?}");
761        })
762    }
763
764    fn is_assumed_constant(&self, const_decl: &fixpoint::ConstDecl) -> bool {
765        if let fixpoint::Var::Const(_, Some(did)) = const_decl.name {
766            return self
767                .genv
768                .constant_info(did)
769                .map(|info| matches!(info, rty::ConstantInfo::Interpreted(..)))
770                .unwrap_or(false);
771        }
772        false
773    }
774
775    fn extract_assumed_consts_aux(
776        &self,
777        cstr: fixpoint::Constraint,
778        acc: &mut HashMap<fixpoint::GlobalVar, fixpoint::Expr>,
779    ) -> fixpoint::Constraint {
780        match cstr {
781            fixpoint::Constraint::ForAll(bind, inner) => {
782                let inner = self.extract_assumed_consts_aux(*inner, acc);
783                if let fixpoint::Pred::Expr(fixpoint::Expr::Atom(fixpoint::BinRel::Eq, operands)) =
784                    bind.pred
785                {
786                    let [left, right] = *operands;
787                    if let fixpoint::Expr::Var(fixpoint::Var::Const(gvar, Some(_))) = left {
788                        acc.insert(gvar, right);
789                        inner
790                    } else {
791                        let bind = fixpoint::Bind {
792                            name: bind.name,
793                            sort: bind.sort,
794                            pred: fixpoint::Pred::Expr(fixpoint::Expr::Atom(
795                                fixpoint::BinRel::Eq,
796                                Box::new([left, right]),
797                            )),
798                        };
799                        fixpoint::Constraint::ForAll(bind, Box::new(inner))
800                    }
801                } else {
802                    fixpoint::Constraint::ForAll(bind, Box::new(inner))
803                }
804            }
805            fixpoint::Constraint::Conj(cstrs) => {
806                fixpoint::Constraint::Conj(
807                    cstrs
808                        .into_iter()
809                        .map(|cstr| self.extract_assumed_consts_aux(cstr, acc))
810                        .collect(),
811                )
812            }
813            fixpoint::Constraint::Pred(..) => cstr,
814        }
815    }
816
817    fn extract_assumed_consts(
818        &self,
819        cstr: fixpoint::Constraint,
820    ) -> (HashMap<fixpoint::GlobalVar, fixpoint::Expr>, fixpoint::Constraint) {
821        let mut acc = HashMap::new();
822        let cstr = self.extract_assumed_consts_aux(cstr, &mut acc);
823        (acc, cstr)
824    }
825
826    fn compute_const_deps(
827        &self,
828        constants: Vec<fixpoint::ConstDecl>,
829        cstr: fixpoint::Constraint,
830    ) -> (ConstDeps, fixpoint::Constraint) {
831        let (mut gvar_eq_map, cstr) = self.extract_assumed_consts(cstr);
832        let mut interpreted = vec![];
833        for decl in constants {
834            if self.is_assumed_constant(&decl) {
835                let fixpoint::Var::Const(gvar, _) = &decl.name else {
836                    unreachable!("assumed constants' names match fixpoint::Var::Const(..)")
837                };
838                let gvar = *gvar;
839                interpreted.push((decl, gvar_eq_map.remove(&gvar).unwrap()));
840            }
841        }
842        let const_deps = ConstDeps { interpreted };
843        (const_deps, cstr)
844    }
845
846    pub fn generate_lean_files(
847        self,
848        def_id: MaybeExternId,
849        task: fixpoint::Task,
850        kvar_solutions: KVarSolutions,
851    ) -> QueryResult {
852        // FIXME(nilehmann) opaque sorts should be part of the task.
853        let opaque_sorts = self.scx.opaque_sorts_to_fixpoint(self.genv);
854        let (const_deps, constraint) = self.compute_const_deps(task.constants, task.constraint);
855        let sort_deps =
856            SortDeps { opaque_sorts, data_decls: task.data_decls, adt_map: self.scx.adt_sorts };
857
858        LeanEncoder::encode(
859            self.genv,
860            def_id,
861            self.ecx.local_var_env.pretty_var_map,
862            sort_deps,
863            task.define_funs,
864            const_deps,
865            task.kvars,
866            constraint,
867            kvar_solutions,
868        )
869        .map_err(|err| query_bug!("could not encode constraint: {err:?}"))
870    }
871
872    fn run_task_with_cache(
873        genv: GlobalEnv,
874        task: &fixpoint::Task,
875        def_id: DefId,
876        kind: FixpointQueryKind,
877        cache: &mut FixQueryCache,
878    ) -> VerificationResult<TagIdx> {
879        let key = kind.task_key(genv.tcx(), def_id);
880
881        let hash = task.hash_with_default();
882
883        if config::is_cache_enabled()
884            && let Some(result) = cache.lookup(&key, hash)
885        {
886            metrics::incr_metric_if(kind.is_body(), Metric::FnCached);
887            return result.clone();
888        }
889        let result = metrics::time_it(TimingKind::FixpointQuery(def_id, kind), || {
890            task.run()
891                .unwrap_or_else(|err| tracked_span_bug!("failed to run fixpoint: {err}"))
892        });
893
894        if config::is_cache_enabled() {
895            cache.insert(key, hash, result.clone());
896        }
897
898        result
899    }
900
901    fn tag_idx(&mut self, tag: Tag) -> TagIdx
902    where
903        Tag: std::fmt::Debug,
904    {
905        *self.tags_inv.entry(tag).or_insert_with(|| {
906            let idx = self.tags.push(tag);
907            self.comments.push(format!("Tag {idx}: {tag:?}"));
908            idx
909        })
910    }
911
912    pub(crate) fn with_early_param(&mut self, param: &EarlyReftParam) {
913        self.ecx
914            .local_var_env
915            .pretty_var_map
916            .set(PrettyVar::Param(*param), Some(param.name));
917    }
918
919    pub(crate) fn with_name_map<R>(
920        &mut self,
921        name: rty::Name,
922        provenance: NameProvenance,
923        f: impl FnOnce(&mut Self, fixpoint::LocalVar) -> R,
924    ) -> R {
925        let fresh = self.ecx.local_var_env.insert_fvar_map(name, provenance);
926        let r = f(self, fresh);
927        self.ecx.local_var_env.remove_fvar_map(name);
928        r
929    }
930
931    pub(crate) fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
932        self.scx.sort_to_fixpoint(sort)
933    }
934
935    pub(crate) fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
936        self.ecx.var_to_fixpoint(var)
937    }
938
939    /// Encodes an expression in head position as a [`fixpoint::Constraint`] "peeling out"
940    /// implications and foralls.
941    ///
942    /// [`fixpoint::Constraint`]: liquid_fixpoint::Constraint
943    pub(crate) fn head_to_fixpoint(
944        &mut self,
945        expr: &rty::Expr,
946        mk_tag: impl Fn(Option<ESpan>) -> Tag + Copy,
947    ) -> QueryResult<fixpoint::Constraint>
948    where
949        Tag: std::fmt::Debug,
950    {
951        match expr.kind() {
952            rty::ExprKind::BinaryOp(rty::BinOp::And, ..) => {
953                // avoid creating nested conjunctions
954                let cstrs = expr
955                    .flatten_conjs()
956                    .into_iter()
957                    .map(|e| self.head_to_fixpoint(e, mk_tag))
958                    .try_collect()?;
959                Ok(fixpoint::Constraint::conj(cstrs))
960            }
961            rty::ExprKind::BinaryOp(rty::BinOp::Imp, e1, e2) => {
962                let (bindings, assumption) = self.assumption_to_fixpoint(e1)?;
963                let cstr = self.head_to_fixpoint(e2, mk_tag)?;
964                Ok(fixpoint::Constraint::foralls(bindings, mk_implies(assumption, cstr)))
965            }
966            rty::ExprKind::KVar(kvar) => {
967                let mut bindings = vec![];
968                let pred = self.kvar_to_fixpoint(kvar, &mut bindings)?;
969                Ok(fixpoint::Constraint::foralls(bindings, fixpoint::Constraint::Pred(pred, None)))
970            }
971            rty::ExprKind::ForAll(pred) => {
972                self.ecx
973                    .local_var_env
974                    .push_layer_with_fresh_names(pred.vars().len());
975                let cstr = self.head_to_fixpoint(pred.as_ref().skip_binder(), mk_tag)?;
976                let vars = self.ecx.local_var_env.pop_layer();
977
978                let bindings = iter::zip(vars, pred.vars())
979                    .map(|(var, kind)| {
980                        fixpoint::Bind {
981                            name: var.into(),
982                            sort: self.scx.sort_to_fixpoint(kind.expect_sort()),
983                            pred: fixpoint::Pred::TRUE,
984                        }
985                    })
986                    .collect_vec();
987
988                Ok(fixpoint::Constraint::foralls(bindings, cstr))
989            }
990            _ => {
991                let tag_idx = self.tag_idx(mk_tag(expr.span()));
992                let pred = fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?);
993                Ok(fixpoint::Constraint::Pred(pred, Some(tag_idx)))
994            }
995        }
996    }
997
998    /// Encodes an expression in assumptive position as a [`fixpoint::Pred`]. Returns the encoded
999    /// predicate and a list of bindings produced by ANF-ing kvars.
1000    ///
1001    /// [`fixpoint::Pred`]: liquid_fixpoint::Pred
1002    pub(crate) fn assumption_to_fixpoint(
1003        &mut self,
1004        pred: &rty::Expr,
1005    ) -> QueryResult<(Vec<fixpoint::Bind>, fixpoint::Pred)> {
1006        let mut bindings = vec![];
1007        let mut preds = vec![];
1008        self.assumption_to_fixpoint_aux(pred, &mut bindings, &mut preds)?;
1009        Ok((bindings, fixpoint::Pred::and(preds)))
1010    }
1011
1012    /// Auxiliary function to merge nested conjunctions in a single predicate
1013    fn assumption_to_fixpoint_aux(
1014        &mut self,
1015        expr: &rty::Expr,
1016        bindings: &mut Vec<fixpoint::Bind>,
1017        preds: &mut Vec<fixpoint::Pred>,
1018    ) -> QueryResult {
1019        match expr.kind() {
1020            rty::ExprKind::BinaryOp(rty::BinOp::And, e1, e2) => {
1021                self.assumption_to_fixpoint_aux(e1, bindings, preds)?;
1022                self.assumption_to_fixpoint_aux(e2, bindings, preds)?;
1023            }
1024            rty::ExprKind::KVar(kvar) => {
1025                preds.push(self.kvar_to_fixpoint(kvar, bindings)?);
1026            }
1027            rty::ExprKind::ForAll(_) => {
1028                // If a forall appears in assumptive position replace it with true. This is sound
1029                // because we are weakening the context, i.e., anything true without the assumption
1030                // should remain true after adding it. Note that this relies on the predicate
1031                // appearing negatively. This is guaranteed by the surface syntax because foralls
1032                // can only appear at the top-level in a requires clause.
1033                preds.push(fixpoint::Pred::TRUE);
1034            }
1035            _ => {
1036                preds.push(fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?));
1037            }
1038        }
1039        Ok(())
1040    }
1041
1042    fn kvar_to_fixpoint(
1043        &mut self,
1044        kvar: &rty::KVar,
1045        bindings: &mut Vec<fixpoint::Bind>,
1046    ) -> QueryResult<fixpoint::Pred> {
1047        let decl = self.kvars.get(kvar.kvid);
1048        let kvids = self.kcx.declare(kvar.kvid, decl, &self.ecx.backend);
1049
1050        let all_args = self.ecx.exprs_to_fixpoint(&kvar.args, &mut self.scx)?;
1051
1052        // Fixpoint doesn't support kvars without arguments, which we do generate sometimes. To get
1053        // around it, we encode `$k()` as ($k 0), or more precisely `(forall ((x int) (= x 0)) ... ($k x)`
1054        // after ANF-ing.
1055        if all_args.is_empty() {
1056            let fresh = self.ecx.local_var_env.fresh_name();
1057            let var = fixpoint::Var::Local(fresh);
1058            bindings.push(fixpoint::Bind {
1059                name: fresh.into(),
1060                sort: fixpoint::Sort::Int,
1061                pred: fixpoint::Pred::Expr(fixpoint::Expr::eq(
1062                    fixpoint::Expr::Var(var),
1063                    fixpoint::Expr::int(0),
1064                )),
1065            });
1066            return Ok(fixpoint::Pred::KVar(kvids.start, vec![fixpoint::Expr::Var(var)]));
1067        }
1068
1069        let kvars = kvids
1070            .enumerate()
1071            .map(|(i, kvid)| {
1072                let args = all_args[i..].to_vec();
1073                fixpoint::Pred::KVar(kvid, args)
1074            })
1075            .collect_vec();
1076
1077        Ok(fixpoint::Pred::And(kvars))
1078    }
1079}
1080
1081fn const_to_fixpoint(cst: rty::Constant) -> fixpoint::Expr {
1082    match cst {
1083        rty::Constant::Int(i) => {
1084            if i.is_negative() {
1085                fixpoint::Expr::Neg(Box::new(fixpoint::Constant::Numeral(i.abs()).into()))
1086            } else {
1087                fixpoint::Constant::Numeral(i.abs()).into()
1088            }
1089        }
1090        rty::Constant::Real(r) => fixpoint::Constant::Real(r.0).into(),
1091        rty::Constant::Bool(b) => fixpoint::Constant::Boolean(b).into(),
1092        rty::Constant::Char(c) => fixpoint::Constant::Numeral(u128::from(c)).into(),
1093        rty::Constant::Str(s) => fixpoint::Constant::String(fixpoint::SymStr(s)).into(),
1094        rty::Constant::BitVec(i, size) => fixpoint::Constant::BitVec(i, size).into(),
1095    }
1096}
1097
1098/// During encoding into fixpoint we generate multiple fixpoint kvars per kvar in flux. A
1099/// [`KVarEncodingCtxt`] is used to keep track of the state needed for this.
1100///
1101/// See [`KVarEncoding`]
1102#[derive(Default)]
1103struct KVarEncodingCtxt {
1104    /// A map from a [`rty::KVid`] to the range of [`fixpoint::KVid`]s that will be used to
1105    /// encode it.
1106    ranges: FxIndexMap<rty::KVid, Range<fixpoint::KVid>>,
1107}
1108
1109impl KVarEncodingCtxt {
1110    /// Declares that a kvar has to be encoded into fixpoint and assigns a range of
1111    /// [`fixpoint::KVid`]'s to it.
1112    fn declare(
1113        &mut self,
1114        kvid: rty::KVid,
1115        decl: &KVarDecl,
1116        backend: &Backend,
1117    ) -> Range<fixpoint::KVid> {
1118        // The start of the next range
1119        let start = self
1120            .ranges
1121            .last()
1122            .map_or(fixpoint::KVid::from_u32(0), |(_, r)| r.end);
1123
1124        self.ranges
1125            .entry(kvid)
1126            .or_insert_with(|| {
1127                let single_encoding = matches!(decl.encoding, KVarEncoding::Single)
1128                    || matches!(backend, Backend::Lean);
1129                if single_encoding {
1130                    start..start + 1
1131                } else {
1132                    let n = usize::max(decl.self_args, 1);
1133                    start..start + n
1134                }
1135            })
1136            .clone()
1137    }
1138
1139    fn encode_kvars(&self, kvars: &KVarGen, scx: &mut SortEncodingCtxt) -> Vec<fixpoint::KVarDecl> {
1140        self.ranges
1141            .iter()
1142            .flat_map(|(orig, range)| {
1143                let mut all_sorts = kvars
1144                    .get(*orig)
1145                    .sorts
1146                    .iter()
1147                    .map(|s| scx.sort_to_fixpoint(s))
1148                    .collect_vec();
1149
1150                // See comment in `kvar_to_fixpoint`
1151                if all_sorts.is_empty() {
1152                    all_sorts = vec![fixpoint::Sort::Int];
1153                }
1154
1155                range.clone().enumerate().map(move |(i, kvid)| {
1156                    let sorts = all_sorts[i..].to_vec();
1157                    fixpoint::KVarDecl::new(kvid, sorts, format!("orig: {:?}", orig))
1158                })
1159            })
1160            .collect()
1161    }
1162
1163    /// For each [`rty::KVid`] `$k`, this function collects all predicates associated
1164    /// with the [`fixpoint::KVid`]s that encode `$k` and combines them into a single
1165    /// predicate by conjoining them.
1166    ///
1167    /// A group (i.e., a combined predicate) is included in the result only if *all*
1168    /// [`fixpoint::KVid`]s in the encoding range of `$k` are present in the input.
1169    fn group_kvar_solution(
1170        &self,
1171        mut items: Vec<(fixpoint::KVid, rty::Binder<rty::Expr>)>,
1172    ) -> FxIndexMap<rty::KVid, rty::Binder<rty::Expr>> {
1173        let mut map = FxIndexMap::default();
1174
1175        items.sort_by_key(|(kvid, _)| *kvid);
1176        items.reverse();
1177
1178        for (orig, range) in &self.ranges {
1179            let mut preds = vec![];
1180            while let Some((_, t)) = items.pop_if(|(k, _)| range.contains(k)) {
1181                preds.push(t);
1182            }
1183            // We only put it in the map if the entire range is present.
1184            if preds.len() == range.end.as_usize() - range.start.as_usize() {
1185                let vars = preds[0].vars().clone();
1186                let conj = rty::Expr::and_from_iter(
1187                    preds
1188                        .into_iter()
1189                        .enumerate()
1190                        .map(|(i, e)| e.skip_binder().shift_horizontally(i)),
1191                );
1192                map.insert(*orig, rty::Binder::bind_with_vars(conj, vars));
1193            }
1194        }
1195        map
1196    }
1197}
1198
1199/// Environment used to map from [`rty::Var`] to a [`fixpoint::LocalVar`].
1200struct LocalVarEnv {
1201    local_var_gen: IndexGen<fixpoint::LocalVar>,
1202    fvars: UnordMap<rty::Name, fixpoint::LocalVar>,
1203    /// Layers of late bound variables
1204    layers: Vec<Vec<fixpoint::LocalVar>>,
1205    /// While it might seem like the signature should be
1206    /// [`UnordMap<fixpoint::LocalVar, rty::Var>`], we encode the arguments to
1207    /// kvars (which can be arbitrary expressions) as local variables; thus we
1208    /// need to keep the output as an [`rty::Expr`] to reflect this.
1209    reverse_map: UnordMap<fixpoint::LocalVar, rty::Expr>,
1210    pretty_var_map: PrettyMap<fixpoint::LocalVar>,
1211}
1212
1213impl LocalVarEnv {
1214    fn new() -> Self {
1215        Self {
1216            local_var_gen: IndexGen::new(),
1217            fvars: Default::default(),
1218            layers: Vec::new(),
1219            reverse_map: Default::default(),
1220            pretty_var_map: PrettyMap::new(),
1221        }
1222    }
1223
1224    // This doesn't require to be mutable because `IndexGen` uses atomics, but we make it mutable
1225    // to better declare the intent.
1226    fn fresh_name(&mut self) -> fixpoint::LocalVar {
1227        self.local_var_gen.fresh()
1228    }
1229
1230    fn insert_fvar_map(
1231        &mut self,
1232        name: rty::Name,
1233        provenance: NameProvenance,
1234    ) -> fixpoint::LocalVar {
1235        let fresh = self.fresh_name();
1236        self.fvars.insert(name, fresh);
1237        self.reverse_map.insert(fresh, rty::Expr::fvar(name));
1238        self.pretty_var_map
1239            .set(PrettyVar::Local(fresh), provenance.opt_symbol());
1240        fresh
1241    }
1242
1243    fn remove_fvar_map(&mut self, name: rty::Name) {
1244        self.fvars.remove(&name);
1245    }
1246
1247    /// Push a layer of bound variables assigning a fresh [`fixpoint::LocalVar`] to each one
1248    fn push_layer_with_fresh_names(&mut self, count: usize) {
1249        let layer = (0..count).map(|_| self.fresh_name()).collect();
1250        self.layers.push(layer);
1251        // FIXME: (ck) what to put in reverse_map here?
1252    }
1253
1254    fn push_layer(&mut self, layer: Vec<fixpoint::LocalVar>) {
1255        self.layers.push(layer);
1256    }
1257
1258    fn pop_layer(&mut self) -> Vec<fixpoint::LocalVar> {
1259        self.layers.pop().unwrap()
1260    }
1261
1262    fn get_fvar(&self, name: rty::Name) -> Option<fixpoint::LocalVar> {
1263        self.fvars.get(&name).copied()
1264    }
1265
1266    fn get_late_bvar(&self, debruijn: DebruijnIndex, var: BoundVar) -> Option<fixpoint::LocalVar> {
1267        let depth = self.layers.len().checked_sub(debruijn.as_usize() + 1)?;
1268        self.layers[depth].get(var.as_usize()).copied()
1269    }
1270}
1271
1272pub struct KVarGen {
1273    kvars: IndexVec<rty::KVid, KVarDecl>,
1274    /// If true, generate dummy [holes] instead of kvars. Used during shape mode to avoid generating
1275    /// unnecessary kvars.
1276    ///
1277    /// [holes]: rty::ExprKind::Hole
1278    dummy: bool,
1279}
1280
1281impl KVarGen {
1282    pub(crate) fn new(dummy: bool) -> Self {
1283        Self { kvars: IndexVec::new(), dummy }
1284    }
1285
1286    fn get(&self, kvid: rty::KVid) -> &KVarDecl {
1287        &self.kvars[kvid]
1288    }
1289
1290    /// Generate a fresh [kvar] under several layers of [binders]. Each layer may contain any kind
1291    /// of bound variable, but variables that are not of kind [`BoundVariableKind::Refine`] will
1292    /// be filtered out.
1293    ///
1294    /// The variables bound in the last layer (last element of the `binders` slice) is expected to
1295    /// have only [`BoundVariableKind::Refine`] and all its elements are used as the [self arguments].
1296    /// The rest of the binders are appended to the `scope`.
1297    ///
1298    /// Note that the returned expression will have escaping variables and it is up to the caller to
1299    /// put it under an appropriate number of binders.
1300    ///
1301    /// Prefer using [`InferCtxt::fresh_kvar`] when possible.
1302    ///
1303    /// [binders]: rty::Binder
1304    /// [kvar]: rty::KVar
1305    /// [`InferCtxt::fresh_kvar`]: crate::infer::InferCtxt::fresh_kvar
1306    /// [self arguments]: rty::KVar::self_args
1307    /// [`BoundVariableKind::Refine`]: rty::BoundVariableKind::Refine
1308    pub fn fresh(
1309        &mut self,
1310        binders: &[rty::BoundVariableKinds],
1311        scope: impl IntoIterator<Item = (rty::Var, rty::Sort)>,
1312        encoding: KVarEncoding,
1313    ) -> rty::Expr {
1314        if self.dummy {
1315            return rty::Expr::hole(rty::HoleKind::Pred);
1316        }
1317
1318        let args = itertools::chain(
1319            binders.iter().rev().enumerate().flat_map(|(level, vars)| {
1320                let debruijn = DebruijnIndex::from_usize(level);
1321                vars.iter()
1322                    .cloned()
1323                    .enumerate()
1324                    .flat_map(move |(idx, var)| {
1325                        if let rty::BoundVariableKind::Refine(sort, _, kind) = var {
1326                            let br = rty::BoundReft { var: BoundVar::from_usize(idx), kind };
1327                            Some((rty::Var::Bound(debruijn, br), sort))
1328                        } else {
1329                            None
1330                        }
1331                    })
1332            }),
1333            scope,
1334        );
1335        let [.., last] = binders else {
1336            return self.fresh_inner(0, [], encoding);
1337        };
1338        let num_self_args = last
1339            .iter()
1340            .filter(|var| matches!(var, rty::BoundVariableKind::Refine(..)))
1341            .count();
1342        self.fresh_inner(num_self_args, args, encoding)
1343    }
1344
1345    fn fresh_inner<A>(&mut self, self_args: usize, args: A, encoding: KVarEncoding) -> rty::Expr
1346    where
1347        A: IntoIterator<Item = (rty::Var, rty::Sort)>,
1348    {
1349        // asset last one has things
1350        let mut sorts = vec![];
1351        let mut exprs = vec![];
1352
1353        let mut flattened_self_args = 0;
1354        for (i, (var, sort)) in args.into_iter().enumerate() {
1355            let is_self_arg = i < self_args;
1356            let var = var.to_expr();
1357            sort.walk(|sort, proj| {
1358                if !matches!(sort, rty::Sort::Loc) {
1359                    flattened_self_args += is_self_arg as usize;
1360                    sorts.push(sort.clone());
1361                    exprs.push(rty::Expr::field_projs(&var, proj));
1362                }
1363            });
1364        }
1365
1366        let kvid = self
1367            .kvars
1368            .push(KVarDecl { self_args: flattened_self_args, sorts, encoding });
1369
1370        let kvar = rty::KVar::new(kvid, flattened_self_args, exprs);
1371        rty::Expr::kvar(kvar)
1372    }
1373}
1374
1375#[derive(Clone)]
1376struct KVarDecl {
1377    self_args: usize,
1378    sorts: Vec<rty::Sort>,
1379    encoding: KVarEncoding,
1380}
1381
1382/// How an [`rty::KVar`] is encoded in the fixpoint constraint
1383#[derive(Clone, Copy)]
1384pub enum KVarEncoding {
1385    /// Generate a single kvar appending the self arguments and the scope, i.e.,
1386    /// a kvar `$k(a0, ...)[b0, ...]` becomes `$k(a0, ..., b0, ...)` in the fixpoint constraint.
1387    Single,
1388    /// Generate a conjunction of kvars, one per argument in [`rty::KVar::args`].
1389    /// Concretely, a kvar `$k(a0, a1, ..., an)[b0, ...]` becomes
1390    /// `$k0(a0, a1, ..., an, b0, ...) ∧ $k1(a1, ..., an, b0, ...) ∧ ... ∧ $kn(an, b0, ...)`
1391    Conj,
1392}
1393
1394impl std::fmt::Display for TagIdx {
1395    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1396        write!(f, "{}", self.as_u32())
1397    }
1398}
1399
1400impl std::str::FromStr for TagIdx {
1401    type Err = std::num::ParseIntError;
1402
1403    fn from_str(s: &str) -> Result<Self, Self::Err> {
1404        Ok(Self::from_u32(s.parse()?))
1405    }
1406}
1407
1408#[derive(Default)]
1409struct ConstEnv<'tcx> {
1410    const_map: ConstMap<'tcx>,
1411    const_map_rev: HashMap<fixpoint::GlobalVar, ConstKey<'tcx>>,
1412    global_var_gen: IndexGen<fixpoint::GlobalVar>,
1413    fun_decl_map: FunDeclMap,
1414}
1415
1416impl<'tcx> ConstEnv<'tcx> {
1417    fn get_or_insert(
1418        &mut self,
1419        key: ConstKey<'tcx>,
1420        // make_name: impl FnOnce() -> fixpoint::GlobalVar,
1421        make_const_decl: impl FnOnce(fixpoint::GlobalVar) -> fixpoint::ConstDecl,
1422    ) -> &fixpoint::ConstDecl {
1423        self.const_map.entry(key.clone()).or_insert_with(|| {
1424            let global_name = self.global_var_gen.fresh();
1425            self.const_map_rev.insert(global_name, key);
1426            make_const_decl(global_name)
1427        })
1428    }
1429}
1430
1431pub struct ExprEncodingCtxt<'genv, 'tcx> {
1432    genv: GlobalEnv<'genv, 'tcx>,
1433    local_var_env: LocalVarEnv,
1434    const_env: ConstEnv<'tcx>,
1435    errors: Errors<'genv>,
1436    /// Id of the item being checked. This is a [`MaybeExternId`] because we may be encoding
1437    /// invariants for an extern spec on an enum.
1438    def_id: Option<MaybeExternId>,
1439    infcx: rustc_infer::infer::InferCtxt<'tcx>,
1440    backend: Backend,
1441}
1442
1443pub struct KVarSolutions {
1444    pub cut_solutions: FxIndexMap<fixpoint::KVid, ClosedSolution>,
1445    pub non_cut_solutions: FxIndexMap<fixpoint::KVid, ClosedSolution>,
1446}
1447
1448impl KVarSolutions {
1449    pub(crate) fn closed_solutions(
1450        variable_sorts: HashMap<fixpoint::Var, fixpoint::Sort>,
1451        cut_solutions: FxIndexMap<fixpoint::KVid, FixpointSolution>,
1452        non_cut_solutions: FxIndexMap<fixpoint::KVid, FixpointSolution>,
1453    ) -> Self {
1454        let closed_cut_solutions = cut_solutions
1455            .into_iter()
1456            .map(|(k, v)| (k, (vec![], v)))
1457            .collect();
1458
1459        let mut closed_non_cut_solutions: FxIndexMap<fixpoint::KVid, ClosedSolution> =
1460            FxIndexMap::default();
1461        for (kvid, mut solution) in non_cut_solutions {
1462            let bound_vars: HashSet<&_> = solution.0.iter().map(|(var, _)| var).collect();
1463            let vars = solution.1.free_vars();
1464            let free_vars = vars.iter().filter(|var| {
1465                !bound_vars.contains(var)
1466                    && !matches!(
1467                        var,
1468                        fixpoint::Var::DataCtor(..)
1469                            | fixpoint::Var::Global(..)
1470                            | fixpoint::Var::DataProj { .. }
1471                    )
1472            });
1473            let free_var_subs = free_vars
1474                .map(|fvar| (*fvar, variable_sorts.get(fvar).unwrap().clone()))
1475                .collect();
1476            solution.1.var_sorts_to_int();
1477            closed_non_cut_solutions.insert(kvid, (free_var_subs, solution));
1478        }
1479        KVarSolutions {
1480            cut_solutions: closed_cut_solutions,
1481            non_cut_solutions: closed_non_cut_solutions,
1482        }
1483    }
1484
1485    pub(crate) fn is_empty(&self) -> bool {
1486        self.cut_solutions.is_empty() && self.non_cut_solutions.is_empty()
1487    }
1488}
1489
1490#[derive(Debug)]
1491pub struct SortDeps {
1492    pub opaque_sorts: Vec<(FluxDefId, fixpoint::SortDecl)>,
1493    pub data_decls: Vec<fixpoint::DataDecl>,
1494    pub adt_map: FxIndexSet<DefId>,
1495}
1496
1497pub struct ConstDeps {
1498    pub interpreted: Vec<InterpretedConst>,
1499}
1500
1501impl<'genv, 'tcx> ExprEncodingCtxt<'genv, 'tcx> {
1502    pub fn new(
1503        genv: GlobalEnv<'genv, 'tcx>,
1504        def_id: Option<MaybeExternId>,
1505        backend: Backend,
1506    ) -> Self {
1507        Self {
1508            genv,
1509            local_var_env: LocalVarEnv::new(),
1510            const_env: Default::default(),
1511            errors: Errors::new(genv.sess()),
1512            def_id,
1513            infcx: genv
1514                .tcx()
1515                .infer_ctxt()
1516                .with_next_trait_solver(true)
1517                .build(TypingMode::non_body_analysis()),
1518            backend,
1519        }
1520    }
1521
1522    fn def_span(&self) -> Span {
1523        self.def_id
1524            .map_or(DUMMY_SP, |def_id| self.genv.tcx().def_span(def_id))
1525    }
1526
1527    fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
1528        match var {
1529            rty::Var::Free(name) => {
1530                self.local_var_env
1531                    .get_fvar(*name)
1532                    .unwrap_or_else(|| {
1533                        span_bug!(self.def_span(), "no entry found for name: `{name:?}`")
1534                    })
1535                    .into()
1536            }
1537            rty::Var::Bound(debruijn, breft) => {
1538                self.local_var_env
1539                    .get_late_bvar(*debruijn, breft.var)
1540                    .unwrap_or_else(|| {
1541                        span_bug!(self.def_span(), "no entry found for late bound var: `{breft:?}`")
1542                    })
1543                    .into()
1544            }
1545            rty::Var::ConstGeneric(param) => fixpoint::Var::ConstGeneric(*param),
1546            rty::Var::EarlyParam(param) => fixpoint::Var::Param(*param),
1547            rty::Var::EVar(_) => {
1548                span_bug!(self.def_span(), "unexpected evar: `{var:?}`")
1549            }
1550        }
1551    }
1552
1553    fn variant_to_fixpoint(
1554        &self,
1555        scx: &mut SortEncodingCtxt,
1556        enum_def_id: &DefId,
1557        idx: VariantIdx,
1558    ) -> fixpoint::Var {
1559        let adt_id = scx.declare_adt(*enum_def_id);
1560        fixpoint::Var::DataCtor(adt_id, idx)
1561    }
1562
1563    fn struct_fields_to_fixpoint(
1564        &mut self,
1565        did: &DefId,
1566        flds: &[rty::Expr],
1567        scx: &mut SortEncodingCtxt,
1568    ) -> QueryResult<fixpoint::Expr> {
1569        // do not generate 1-tuples
1570        if let [fld] = flds {
1571            self.expr_to_fixpoint(fld, scx)
1572        } else {
1573            let adt_id = scx.declare_adt(*did);
1574            let ctor = fixpoint::Expr::Var(fixpoint::Var::DataCtor(adt_id, VariantIdx::ZERO));
1575            let args = flds
1576                .iter()
1577                .map(|fld| self.expr_to_fixpoint(fld, scx))
1578                .try_collect()?;
1579            Ok(fixpoint::Expr::App(Box::new(ctor), None, args, None))
1580        }
1581    }
1582
1583    fn fields_to_fixpoint(
1584        &mut self,
1585        flds: &[rty::Expr],
1586        scx: &mut SortEncodingCtxt,
1587    ) -> QueryResult<fixpoint::Expr> {
1588        // do not generate 1-tuples
1589        if let [fld] = flds {
1590            self.expr_to_fixpoint(fld, scx)
1591        } else {
1592            scx.declare_tuple(flds.len());
1593            let ctor = fixpoint::Expr::Var(fixpoint::Var::TupleCtor { arity: flds.len() });
1594            let args = flds
1595                .iter()
1596                .map(|fld| self.expr_to_fixpoint(fld, scx))
1597                .try_collect()?;
1598            Ok(fixpoint::Expr::App(Box::new(ctor), None, args, None))
1599        }
1600    }
1601
1602    fn internal_func_to_fixpoint(
1603        &mut self,
1604        internal_func: &InternalFuncKind,
1605        sort_args: &[rty::SortArg],
1606        args: &[rty::Expr],
1607        scx: &mut SortEncodingCtxt,
1608    ) -> QueryResult<fixpoint::Expr> {
1609        match internal_func {
1610            InternalFuncKind::Val(op) => {
1611                let func = fixpoint::Expr::Var(self.define_const_for_prim_op(op, scx));
1612                let args = self.exprs_to_fixpoint(args, scx)?;
1613                Ok(fixpoint::Expr::App(Box::new(func), None, args, None))
1614            }
1615            InternalFuncKind::Rel(op) => {
1616                let expr = if let Some(prim_rel) = self.genv.prim_rel_for(op)? {
1617                    prim_rel.body.replace_bound_refts(args)
1618                } else {
1619                    rty::Expr::tt()
1620                };
1621                self.expr_to_fixpoint(&expr, scx)
1622            }
1623            InternalFuncKind::Cast => {
1624                let [rty::SortArg::Sort(from), rty::SortArg::Sort(to)] = &sort_args else {
1625                    span_bug!(self.def_span(), "unexpected cast")
1626                };
1627                match from.cast_kind(to) {
1628                    rty::CastKind::Identity => self.expr_to_fixpoint(&args[0], scx),
1629                    rty::CastKind::BoolToInt => {
1630                        Ok(fixpoint::Expr::IfThenElse(Box::new([
1631                            self.expr_to_fixpoint(&args[0], scx)?,
1632                            fixpoint::Expr::int(1),
1633                            fixpoint::Expr::int(0),
1634                        ])))
1635                    }
1636                    rty::CastKind::IntoUnit => self.expr_to_fixpoint(&rty::Expr::unit(), scx),
1637                    rty::CastKind::Uninterpreted => {
1638                        let func = fixpoint::Expr::Var(self.define_const_for_cast(from, to, scx));
1639                        let args = self.exprs_to_fixpoint(args, scx)?;
1640                        Ok(fixpoint::Expr::App(Box::new(func), None, args, None))
1641                    }
1642                }
1643            }
1644            InternalFuncKind::PtrSize => {
1645                let func = fixpoint::Expr::Var(self.define_const_for_ptr_size(scx));
1646                let args = self.exprs_to_fixpoint(args, scx)?;
1647                Ok(fixpoint::Expr::App(Box::new(func), None, args, None))
1648            }
1649        }
1650    }
1651
1652    fn structurally_normalize_expr(&self, expr: &rty::Expr) -> QueryResult<rty::Expr> {
1653        if let Some(def_id) = self.def_id {
1654            structurally_normalize_expr(self.genv, def_id.resolved_id(), &self.infcx, expr)
1655        } else {
1656            Ok(expr.clone())
1657        }
1658    }
1659
1660    fn expr_to_fixpoint(
1661        &mut self,
1662        expr: &rty::Expr,
1663        scx: &mut SortEncodingCtxt,
1664    ) -> QueryResult<fixpoint::Expr> {
1665        let expr = self.structurally_normalize_expr(expr)?;
1666        let e = match expr.kind() {
1667            rty::ExprKind::Var(var) => fixpoint::Expr::Var(self.var_to_fixpoint(var)),
1668            rty::ExprKind::Constant(c) => const_to_fixpoint(*c),
1669            rty::ExprKind::BinaryOp(op, e1, e2) => self.bin_op_to_fixpoint(op, e1, e2, scx)?,
1670            rty::ExprKind::UnaryOp(op, e) => self.un_op_to_fixpoint(*op, e, scx)?,
1671            rty::ExprKind::FieldProj(e, proj) => self.proj_to_fixpoint(e, *proj, scx)?,
1672            rty::ExprKind::Tuple(flds) => self.fields_to_fixpoint(flds, scx)?,
1673            rty::ExprKind::Ctor(rty::Ctor::Struct(did), flds) => {
1674                self.struct_fields_to_fixpoint(did, flds, scx)?
1675            }
1676            rty::ExprKind::IsCtor(def_id, variant_idx, e) => {
1677                let ctor = self.variant_to_fixpoint(scx, def_id, *variant_idx);
1678                let e = self.expr_to_fixpoint(e, scx)?;
1679                fixpoint::Expr::IsCtor(ctor, Box::new(e))
1680            }
1681            rty::ExprKind::Ctor(rty::Ctor::Enum(did, idx), args) => {
1682                let ctor = self.variant_to_fixpoint(scx, did, *idx);
1683                let args = self.exprs_to_fixpoint(args, scx)?;
1684                fixpoint::Expr::App(Box::new(fixpoint::Expr::Var(ctor)), None, args, None)
1685            }
1686            rty::ExprKind::ConstDefId(did) => {
1687                let var = self.define_const_for_rust_const(*did, scx);
1688                fixpoint::Expr::Var(var)
1689            }
1690            rty::ExprKind::App(func, sort_args, args) => {
1691                if let rty::ExprKind::InternalFunc(func) = func.kind() {
1692                    self.internal_func_to_fixpoint(func, sort_args, args, scx)?
1693                } else {
1694                    let func = self.expr_to_fixpoint(func, scx)?;
1695                    let sort_args = self.sort_args_to_fixpoint(sort_args, scx);
1696                    let args = self.exprs_to_fixpoint(args, scx)?;
1697                    fixpoint::Expr::App(Box::new(func), Some(sort_args), args, None)
1698                }
1699            }
1700            rty::ExprKind::IfThenElse(p, e1, e2) => {
1701                fixpoint::Expr::IfThenElse(Box::new([
1702                    self.expr_to_fixpoint(p, scx)?,
1703                    self.expr_to_fixpoint(e1, scx)?,
1704                    self.expr_to_fixpoint(e2, scx)?,
1705                ]))
1706            }
1707            rty::ExprKind::Alias(alias_reft, args) => {
1708                let sort = self.genv.sort_of_assoc_reft(alias_reft.assoc_id)?;
1709                let sort = sort.instantiate_identity();
1710                let func =
1711                    fixpoint::Expr::Var(self.define_const_for_alias_reft(alias_reft, sort, scx));
1712                let args = args
1713                    .iter()
1714                    .map(|expr| self.expr_to_fixpoint(expr, scx))
1715                    .try_collect()?;
1716                fixpoint::Expr::App(Box::new(func), None, args, None)
1717            }
1718            rty::ExprKind::Abs(lam) => {
1719                let var = self.define_const_for_lambda(lam, scx);
1720                fixpoint::Expr::Var(var)
1721            }
1722            rty::ExprKind::Let(init, body) => {
1723                debug_assert_eq!(body.vars().len(), 1);
1724                let init = self.expr_to_fixpoint(init, scx)?;
1725
1726                self.local_var_env.push_layer_with_fresh_names(1);
1727                let body = self.expr_to_fixpoint(body.skip_binder_ref(), scx)?;
1728                let vars = self.local_var_env.pop_layer();
1729
1730                fixpoint::Expr::Let(vars[0].into(), Box::new([init, body]))
1731            }
1732            rty::ExprKind::GlobalFunc(SpecFuncKind::Thy(itf)) => fixpoint::Expr::ThyFunc(*itf),
1733            rty::ExprKind::GlobalFunc(SpecFuncKind::Def(def_id)) => {
1734                fixpoint::Expr::Var(self.declare_fun(*def_id))
1735            }
1736            rty::ExprKind::Exists(expr) => {
1737                let expr = self.body_to_fixpoint(expr, scx)?;
1738                fixpoint::Expr::Exists(expr.0, Box::new(expr.1))
1739            }
1740            rty::ExprKind::Hole(..)
1741            | rty::ExprKind::KVar(_)
1742            | rty::ExprKind::Local(_)
1743            | rty::ExprKind::PathProj(..)
1744            | rty::ExprKind::ForAll(_)
1745            | rty::ExprKind::InternalFunc(_) => {
1746                span_bug!(self.def_span(), "unexpected expr: `{expr:?}`")
1747            }
1748            rty::ExprKind::BoundedQuant(kind, rng, body) => {
1749                let exprs = (rng.start..rng.end).map(|i| {
1750                    let arg = rty::Expr::constant(rty::Constant::from(i));
1751                    body.replace_bound_reft(&arg)
1752                });
1753                let expr = match kind {
1754                    flux_middle::fhir::QuantKind::Forall => rty::Expr::and_from_iter(exprs),
1755                    flux_middle::fhir::QuantKind::Exists => rty::Expr::or_from_iter(exprs),
1756                };
1757                self.expr_to_fixpoint(&expr, scx)?
1758            }
1759        };
1760        Ok(e)
1761    }
1762
1763    fn sort_args_to_fixpoint(
1764        &mut self,
1765        sort_args: &[rty::SortArg],
1766        scx: &mut SortEncodingCtxt,
1767    ) -> Vec<fixpoint::Sort> {
1768        sort_args
1769            .iter()
1770            .map(|s_arg| self.sort_arg_to_fixpoint(s_arg, scx))
1771            .collect()
1772    }
1773
1774    fn sort_arg_to_fixpoint(
1775        &mut self,
1776        sort_arg: &rty::SortArg,
1777        scx: &mut SortEncodingCtxt,
1778    ) -> fixpoint::Sort {
1779        match sort_arg {
1780            rty::SortArg::Sort(sort) => scx.sort_to_fixpoint(sort),
1781            rty::SortArg::BvSize(sz) => bv_size_to_fixpoint(*sz),
1782        }
1783    }
1784
1785    fn exprs_to_fixpoint<'b>(
1786        &mut self,
1787        exprs: impl IntoIterator<Item = &'b rty::Expr>,
1788        scx: &mut SortEncodingCtxt,
1789    ) -> QueryResult<Vec<fixpoint::Expr>> {
1790        exprs
1791            .into_iter()
1792            .map(|e| self.expr_to_fixpoint(e, scx))
1793            .try_collect()
1794    }
1795
1796    fn proj_to_fixpoint(
1797        &mut self,
1798        e: &rty::Expr,
1799        proj: rty::FieldProj,
1800        scx: &mut SortEncodingCtxt,
1801    ) -> QueryResult<fixpoint::Expr> {
1802        let arity = proj.arity(self.genv)?;
1803        // we encode 1-tuples as the single element inside so no projection necessary here
1804        if arity == 1 {
1805            self.expr_to_fixpoint(e, scx)
1806        } else {
1807            let proj = match proj {
1808                rty::FieldProj::Tuple { arity, field } => {
1809                    scx.declare_tuple(arity);
1810                    fixpoint::Var::TupleProj { arity, field }
1811                }
1812                rty::FieldProj::Adt { def_id, field } => {
1813                    let adt_id = scx.declare_adt(def_id);
1814                    fixpoint::Var::DataProj { adt_id, field }
1815                }
1816            };
1817            let proj = fixpoint::Expr::Var(proj);
1818            Ok(fixpoint::Expr::App(
1819                Box::new(proj),
1820                None,
1821                vec![self.expr_to_fixpoint(e, scx)?],
1822                None,
1823            ))
1824        }
1825    }
1826
1827    fn un_op_to_fixpoint(
1828        &mut self,
1829        op: rty::UnOp,
1830        e: &rty::Expr,
1831        scx: &mut SortEncodingCtxt,
1832    ) -> QueryResult<fixpoint::Expr> {
1833        match op {
1834            rty::UnOp::Not => Ok(fixpoint::Expr::Not(Box::new(self.expr_to_fixpoint(e, scx)?))),
1835            rty::UnOp::Neg => Ok(fixpoint::Expr::Neg(Box::new(self.expr_to_fixpoint(e, scx)?))),
1836        }
1837    }
1838
1839    fn bv_rel_to_fixpoint(&self, rel: &fixpoint::BinRel) -> fixpoint::Expr {
1840        let itf = match rel {
1841            fixpoint::BinRel::Gt => fixpoint::ThyFunc::BvUgt,
1842            fixpoint::BinRel::Ge => fixpoint::ThyFunc::BvUge,
1843            fixpoint::BinRel::Lt => fixpoint::ThyFunc::BvUlt,
1844            fixpoint::BinRel::Le => fixpoint::ThyFunc::BvUle,
1845            _ => span_bug!(self.def_span(), "not a bitvector relation!"),
1846        };
1847        fixpoint::Expr::ThyFunc(itf)
1848    }
1849
1850    fn set_op_to_fixpoint(&self, op: &rty::BinOp) -> fixpoint::Expr {
1851        let itf = match op {
1852            rty::BinOp::Sub(_) => fixpoint::ThyFunc::SetDif,
1853            rty::BinOp::BitAnd(_) => fixpoint::ThyFunc::SetCap,
1854            rty::BinOp::BitOr(_) => fixpoint::ThyFunc::SetCup,
1855            _ => span_bug!(self.def_span(), "not a set operation!"),
1856        };
1857        fixpoint::Expr::ThyFunc(itf)
1858    }
1859
1860    fn bv_op_to_fixpoint(&self, op: &rty::BinOp) -> fixpoint::Expr {
1861        let itf = match op {
1862            rty::BinOp::Add(_) => fixpoint::ThyFunc::BvAdd,
1863            rty::BinOp::Sub(_) => fixpoint::ThyFunc::BvSub,
1864            rty::BinOp::Mul(_) => fixpoint::ThyFunc::BvMul,
1865            rty::BinOp::Div(_) => fixpoint::ThyFunc::BvUdiv,
1866            rty::BinOp::Mod(_) => fixpoint::ThyFunc::BvUrem,
1867            rty::BinOp::BitAnd(_) => fixpoint::ThyFunc::BvAnd,
1868            rty::BinOp::BitOr(_) => fixpoint::ThyFunc::BvOr,
1869            rty::BinOp::BitXor(_) => fixpoint::ThyFunc::BvXor,
1870            rty::BinOp::BitShl(_) => fixpoint::ThyFunc::BvShl,
1871            rty::BinOp::BitShr(_) => fixpoint::ThyFunc::BvLshr,
1872            _ => span_bug!(self.def_span(), "not a bitvector operation!"),
1873        };
1874        fixpoint::Expr::ThyFunc(itf)
1875    }
1876
1877    fn bin_op_to_fixpoint(
1878        &mut self,
1879        op: &rty::BinOp,
1880        e1: &rty::Expr,
1881        e2: &rty::Expr,
1882        scx: &mut SortEncodingCtxt,
1883    ) -> QueryResult<fixpoint::Expr> {
1884        let op = match op {
1885            rty::BinOp::Eq => {
1886                return Ok(fixpoint::Expr::Atom(
1887                    fixpoint::BinRel::Eq,
1888                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1889                ));
1890            }
1891            rty::BinOp::Ne => {
1892                return Ok(fixpoint::Expr::Atom(
1893                    fixpoint::BinRel::Ne,
1894                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1895                ));
1896            }
1897            rty::BinOp::Gt(sort) => {
1898                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Gt, e1, e2, scx);
1899            }
1900            rty::BinOp::Ge(sort) => {
1901                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Ge, e1, e2, scx);
1902            }
1903            rty::BinOp::Lt(sort) => {
1904                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Lt, e1, e2, scx);
1905            }
1906            rty::BinOp::Le(sort) => {
1907                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Le, e1, e2, scx);
1908            }
1909            rty::BinOp::And => {
1910                return Ok(fixpoint::Expr::And(vec![
1911                    self.expr_to_fixpoint(e1, scx)?,
1912                    self.expr_to_fixpoint(e2, scx)?,
1913                ]));
1914            }
1915            rty::BinOp::Or => {
1916                return Ok(fixpoint::Expr::Or(vec![
1917                    self.expr_to_fixpoint(e1, scx)?,
1918                    self.expr_to_fixpoint(e2, scx)?,
1919                ]));
1920            }
1921            rty::BinOp::Imp => {
1922                return Ok(fixpoint::Expr::Imp(Box::new([
1923                    self.expr_to_fixpoint(e1, scx)?,
1924                    self.expr_to_fixpoint(e2, scx)?,
1925                ])));
1926            }
1927            rty::BinOp::Iff => {
1928                return Ok(fixpoint::Expr::Iff(Box::new([
1929                    self.expr_to_fixpoint(e1, scx)?,
1930                    self.expr_to_fixpoint(e2, scx)?,
1931                ])));
1932            }
1933
1934            // Bit vector operations
1935            rty::BinOp::Add(rty::Sort::BitVec(_))
1936            | rty::BinOp::Sub(rty::Sort::BitVec(_))
1937            | rty::BinOp::Mul(rty::Sort::BitVec(_))
1938            | rty::BinOp::Div(rty::Sort::BitVec(_))
1939            | rty::BinOp::Mod(rty::Sort::BitVec(_))
1940            | rty::BinOp::BitAnd(rty::Sort::BitVec(_))
1941            | rty::BinOp::BitOr(rty::Sort::BitVec(_))
1942            | rty::BinOp::BitXor(rty::Sort::BitVec(_))
1943            | rty::BinOp::BitShl(rty::Sort::BitVec(_))
1944            | rty::BinOp::BitShr(rty::Sort::BitVec(_)) => {
1945                let bv_func = self.bv_op_to_fixpoint(op);
1946                return Ok(fixpoint::Expr::App(
1947                    Box::new(bv_func),
1948                    None,
1949                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1950                    None,
1951                ));
1952            }
1953
1954            // Set operations
1955            rty::BinOp::Sub(rty::Sort::App(rty::SortCtor::Set, _))
1956            | rty::BinOp::BitAnd(rty::Sort::App(rty::SortCtor::Set, _))
1957            | rty::BinOp::BitOr(rty::Sort::App(rty::SortCtor::Set, _)) => {
1958                let set_func = self.set_op_to_fixpoint(op);
1959                return Ok(fixpoint::Expr::App(
1960                    Box::new(set_func),
1961                    None,
1962                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1963                    None,
1964                ));
1965            }
1966
1967            // Interpreted arithmetic operations
1968            rty::BinOp::Add(_) => fixpoint::BinOp::Add,
1969            rty::BinOp::Sub(_) => fixpoint::BinOp::Sub,
1970            rty::BinOp::Mul(_) => fixpoint::BinOp::Mul,
1971            rty::BinOp::Div(_) => fixpoint::BinOp::Div,
1972            rty::BinOp::Mod(_) => fixpoint::BinOp::Mod,
1973
1974            rty::BinOp::BitAnd(sort)
1975            | rty::BinOp::BitOr(sort)
1976            | rty::BinOp::BitXor(sort)
1977            | rty::BinOp::BitShl(sort)
1978            | rty::BinOp::BitShr(sort) => {
1979                bug!("unsupported operation `{op:?}` for sort `{sort:?}`");
1980            }
1981        };
1982        Ok(fixpoint::Expr::BinaryOp(
1983            op,
1984            Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1985        ))
1986    }
1987
1988    /// A binary relation is encoded as a structurally recursive relation between aggregate sorts.
1989    /// For "leaf" expressions, we encode them as an interpreted relation if the sort supports it,
1990    /// otherwise we use an uninterpreted function. For example, consider the following relation
1991    /// between two tuples of sort `(int, int -> int)`
1992    /// ```text
1993    /// (0, λv. v + 1) <= (1, λv. v + 1)
1994    /// ```
1995    /// The encoding in fixpoint will be
1996    ///
1997    /// ```text
1998    /// 0 <= 1 && (le (λv. v + 1) (λv. v + 1))
1999    /// ```
2000    /// Where `<=` is the (interpreted) less than or equal relation between integers and `le` is
2001    /// an uninterpreted relation between ([the encoding] of) lambdas.
2002    ///
2003    /// [the encoding]: Self::define_const_for_lambda
2004    fn bin_rel_to_fixpoint(
2005        &mut self,
2006        sort: &rty::Sort,
2007        rel: fixpoint::BinRel,
2008        e1: &rty::Expr,
2009        e2: &rty::Expr,
2010        scx: &mut SortEncodingCtxt,
2011    ) -> QueryResult<fixpoint::Expr> {
2012        let e = match sort {
2013            rty::Sort::Int | rty::Sort::Real | rty::Sort::Char => {
2014                fixpoint::Expr::Atom(
2015                    rel,
2016                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
2017                )
2018            }
2019            rty::Sort::BitVec(_) => {
2020                let e1 = self.expr_to_fixpoint(e1, scx)?;
2021                let e2 = self.expr_to_fixpoint(e2, scx)?;
2022                let rel = self.bv_rel_to_fixpoint(&rel);
2023                fixpoint::Expr::App(Box::new(rel), None, vec![e1, e2], None)
2024            }
2025            rty::Sort::Tuple(sorts) => {
2026                let arity = sorts.len();
2027                self.apply_bin_rel_rec(sorts, rel, e1, e2, scx, |field| {
2028                    rty::FieldProj::Tuple { arity, field }
2029                })?
2030            }
2031            rty::Sort::App(rty::SortCtor::Adt(sort_def), args)
2032                if let Some(variant) = sort_def.opt_struct_variant() =>
2033            {
2034                let def_id = sort_def.did();
2035                let sorts = variant.field_sorts(args);
2036                self.apply_bin_rel_rec(&sorts, rel, e1, e2, scx, |field| {
2037                    rty::FieldProj::Adt { def_id, field }
2038                })?
2039            }
2040            _ => {
2041                let rel = fixpoint::Expr::Var(fixpoint::Var::UIFRel(rel));
2042                fixpoint::Expr::App(
2043                    Box::new(rel),
2044                    None,
2045                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
2046                    None,
2047                )
2048            }
2049        };
2050        Ok(e)
2051    }
2052
2053    /// Apply binary relation recursively over aggregate expressions
2054    fn apply_bin_rel_rec(
2055        &mut self,
2056        sorts: &[rty::Sort],
2057        rel: fixpoint::BinRel,
2058        e1: &rty::Expr,
2059        e2: &rty::Expr,
2060        scx: &mut SortEncodingCtxt,
2061        mk_proj: impl Fn(u32) -> rty::FieldProj,
2062    ) -> QueryResult<fixpoint::Expr> {
2063        Ok(fixpoint::Expr::and(
2064            sorts
2065                .iter()
2066                .enumerate()
2067                .map(|(idx, s)| {
2068                    let proj = mk_proj(idx as u32);
2069                    let e1 = e1.proj_and_reduce(proj);
2070                    let e2 = e2.proj_and_reduce(proj);
2071                    self.bin_rel_to_fixpoint(s, rel, &e1, &e2, scx)
2072                })
2073                .try_collect()?,
2074        ))
2075    }
2076
2077    /// Declare that the `def_id` of a Flux function (potentially a UIF) needs to be
2078    /// encoded and assigns a name to it if it hasn't yet been declared. The encoding of the
2079    /// function body happens in [`Self::define_funs`].
2080    pub fn declare_fun(&mut self, def_id: FluxDefId) -> fixpoint::Var {
2081        *self
2082            .const_env
2083            .fun_decl_map
2084            .entry(def_id)
2085            .or_insert_with(|| {
2086                let id = self.const_env.global_var_gen.fresh();
2087                fixpoint::Var::Global(id, def_id)
2088            })
2089    }
2090
2091    /// The logic below is a bit "duplicated" with the `prim_op_sort` in `sortck.rs`;
2092    /// They are not exactly the same because this is on rty and the other one on fhir.
2093    /// We should make sure these two remain in sync.
2094    ///
2095    /// (NOTE:PrimOpSort) We are somewhat "overloading" the `BinOps`: as we are using them
2096    /// for (a) interpreted operations on bit vectors AND (b) uninterpreted functions on integers.
2097    /// So when Binop::BitShr (a) appears in a ExprKind::BinOp, it means bit vectors, but
2098    /// (b) inside ExprKind::InternalFunc it means int.
2099    fn prim_op_sort(op: &rty::BinOp, span: Span) -> rty::PolyFuncSort {
2100        match op {
2101            rty::BinOp::BitAnd(rty::Sort::Int)
2102            | rty::BinOp::BitOr(rty::Sort::Int)
2103            | rty::BinOp::BitXor(rty::Sort::Int)
2104            | rty::BinOp::BitShl(rty::Sort::Int)
2105            | rty::BinOp::BitShr(rty::Sort::Int) => {
2106                let fsort =
2107                    rty::FuncSort::new(vec![rty::Sort::Int, rty::Sort::Int], rty::Sort::Int);
2108                rty::PolyFuncSort::new(List::empty(), fsort)
2109            }
2110            _ => span_bug!(span, "unexpected prim op: {op:?} in `prim_op_sort`"),
2111        }
2112    }
2113
2114    fn define_const_for_cast(
2115        &mut self,
2116        from: &rty::Sort,
2117        to: &rty::Sort,
2118        scx: &mut SortEncodingCtxt,
2119    ) -> fixpoint::Var {
2120        let key = ConstKey::Cast(from.clone(), to.clone());
2121        self.const_env
2122            .get_or_insert(key, |global_name| {
2123                let fsort = rty::FuncSort::new(vec![from.clone()], to.clone());
2124                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
2125                let sort = scx.func_sort_to_fixpoint(&fsort).into_sort();
2126                fixpoint::ConstDecl {
2127                    name: fixpoint::Var::Const(global_name, None),
2128                    sort,
2129                    comment: Some(format!("cast uif: ({from:?}) -> {to:?}")),
2130                }
2131            })
2132            .name
2133    }
2134
2135    fn define_const_for_ptr_size(&mut self, scx: &mut SortEncodingCtxt) -> fixpoint::Var {
2136        let key = ConstKey::PtrSize;
2137        self.const_env
2138            .get_or_insert(key, |global_name| {
2139                let fsort = rty::FuncSort::new(vec![rty::Sort::RawPtr], rty::Sort::Int);
2140                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
2141                let sort = scx.func_sort_to_fixpoint(&fsort).into_sort();
2142                fixpoint::ConstDecl {
2143                    name: fixpoint::Var::Const(global_name, None),
2144                    sort,
2145                    comment: Some("ptr_size uif: RawPtr -> Int".to_string()),
2146                }
2147            })
2148            .name
2149    }
2150
2151    fn define_const_for_prim_op(
2152        &mut self,
2153        op: &rty::BinOp,
2154        scx: &mut SortEncodingCtxt,
2155    ) -> fixpoint::Var {
2156        let key = ConstKey::PrimOp(op.clone());
2157        let span = self.def_span();
2158        self.const_env
2159            .get_or_insert(key, |global_name| {
2160                let sort = scx
2161                    .func_sort_to_fixpoint(&Self::prim_op_sort(op, span))
2162                    .into_sort();
2163                fixpoint::ConstDecl {
2164                    name: fixpoint::Var::Const(global_name, None),
2165                    sort,
2166                    comment: Some(format!("prim op uif: {op:?}")),
2167                }
2168            })
2169            .name
2170    }
2171
2172    fn define_const_for_rust_const(
2173        &mut self,
2174        def_id: DefId,
2175        scx: &mut SortEncodingCtxt,
2176    ) -> fixpoint::Var {
2177        let key = ConstKey::RustConst(def_id);
2178        self.const_env
2179            .get_or_insert(key, |global_name| {
2180                let sort = self.genv.sort_of_def_id(def_id).unwrap().unwrap();
2181                fixpoint::ConstDecl {
2182                    name: fixpoint::Var::Const(global_name, Some(def_id)),
2183                    sort: scx.sort_to_fixpoint(&sort),
2184                    comment: Some(format!("rust const: {}", def_id_to_string(def_id))),
2185                }
2186            })
2187            .name
2188    }
2189
2190    /// returns the 'constant' UIF for Var used to represent the alias_pred, creating and adding it
2191    /// to the const_map if necessary
2192    fn define_const_for_alias_reft(
2193        &mut self,
2194        alias_reft: &rty::AliasReft,
2195        fsort: rty::FuncSort,
2196        scx: &mut SortEncodingCtxt,
2197    ) -> fixpoint::Var {
2198        let tcx = self.genv.tcx();
2199        let args = alias_reft
2200            .args
2201            .to_rustc(tcx)
2202            .truncate_to(tcx, tcx.generics_of(alias_reft.assoc_id.parent()));
2203        // See <https://github.com/flux-rs/flux/issues/1510#issuecomment-3953871782> as an example
2204        // for why we erase regions.
2205        let args = tcx.erase_and_anonymize_regions(args);
2206        let key = ConstKey::Alias(alias_reft.assoc_id, args);
2207        self.const_env
2208            .get_or_insert(key, |global_name| {
2209                let comment = Some(format!("alias reft: {alias_reft:?}"));
2210                let name = fixpoint::Var::Const(global_name, None);
2211                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
2212                let sort = scx.func_sort_to_fixpoint(&fsort).into_sort();
2213                fixpoint::ConstDecl { name, comment, sort }
2214            })
2215            .name
2216    }
2217
2218    /// We encode lambdas with uninterpreted constant. Two syntactically equal lambdas will be encoded
2219    /// with the same constant.
2220    fn define_const_for_lambda(
2221        &mut self,
2222        lam: &rty::Lambda,
2223        scx: &mut SortEncodingCtxt,
2224    ) -> fixpoint::Var {
2225        let key = ConstKey::Lambda(lam.clone());
2226        self.const_env
2227            .get_or_insert(key, |global_name| {
2228                let comment = Some(format!("lambda: {lam:?}"));
2229                let name = fixpoint::Var::Const(global_name, None);
2230                let sort = scx
2231                    .func_sort_to_fixpoint(&lam.fsort().to_poly())
2232                    .into_sort();
2233                fixpoint::ConstDecl { name, comment, sort }
2234            })
2235            .name
2236    }
2237
2238    fn assume_const_values(
2239        &mut self,
2240        mut constraint: fixpoint::Constraint,
2241        scx: &mut SortEncodingCtxt,
2242    ) -> QueryResult<fixpoint::Constraint> {
2243        // Encoding the value for a constant could in theory define more constants for which
2244        // we need to assume values, so we iterate until there are no more constants.
2245        let mut idx = 0;
2246        while let Some((key, const_)) = self.const_env.const_map.get_index(idx) {
2247            idx += 1;
2248
2249            match key {
2250                ConstKey::RustConst(def_id) => {
2251                    let info = self.genv.constant_info(def_id)?;
2252                    match info {
2253                        rty::ConstantInfo::Uninterpreted => {}
2254                        rty::ConstantInfo::Interpreted(val, _) => {
2255                            let const_name = const_.name;
2256                            let const_sort = const_.sort.clone();
2257
2258                            let e1 = fixpoint::Expr::Var(const_.name);
2259                            let e2 = self.expr_to_fixpoint(&val, scx)?;
2260                            let pred = fixpoint::Pred::Expr(e1.eq(e2));
2261
2262                            let bind = match self.backend {
2263                                Backend::Fixpoint => {
2264                                    fixpoint::Bind {
2265                                        name: fixpoint::Var::Underscore,
2266                                        sort: fixpoint::Sort::Int,
2267                                        pred,
2268                                    }
2269                                }
2270                                Backend::Lean => {
2271                                    fixpoint::Bind { name: const_name, sort: const_sort, pred }
2272                                }
2273                            };
2274                            constraint = fixpoint::Constraint::ForAll(bind, Box::new(constraint));
2275                        }
2276                    }
2277                }
2278                ConstKey::Alias(..) if matches!(self.backend, Backend::Lean) => {
2279                    constraint = fixpoint::Constraint::ForAll(
2280                        fixpoint::Bind {
2281                            name: const_.name,
2282                            sort: const_.sort.clone(),
2283                            pred: fixpoint::Pred::TRUE,
2284                        },
2285                        Box::new(constraint),
2286                    );
2287                }
2288                ConstKey::Alias(..)
2289                | ConstKey::Cast(..)
2290                | ConstKey::Lambda(..)
2291                | ConstKey::PrimOp(..)
2292                | ConstKey::PtrSize => {}
2293            }
2294        }
2295        Ok(constraint)
2296    }
2297
2298    fn qualifiers_for(
2299        &mut self,
2300        def_id: LocalDefId,
2301        scx: &mut SortEncodingCtxt,
2302    ) -> QueryResult<Vec<fixpoint::Qualifier>> {
2303        self.genv
2304            .qualifiers_for(def_id)?
2305            .map(|qual| self.qualifier_to_fixpoint(qual, scx))
2306            .try_collect()
2307    }
2308
2309    fn define_funs(
2310        &mut self,
2311        def_id: MaybeExternId,
2312        scx: &mut SortEncodingCtxt,
2313    ) -> QueryResult<Vec<fixpoint::FunDef>> {
2314        let reveals: UnordSet<FluxDefId> = self
2315            .genv
2316            .reveals_for(def_id.local_id())
2317            .iter()
2318            .copied()
2319            .collect();
2320        let proven_externally = self.genv.proven_externally(def_id.local_id());
2321        let mut defs = vec![];
2322
2323        // Iterate till encoding the body of functions doesn't require any more functions to be encoded.
2324        let mut idx = 0;
2325        while let Some((&did, _)) = self.const_env.fun_decl_map.get_index(idx) {
2326            idx += 1;
2327
2328            let info = self.genv.normalized_info(did);
2329            let revealed = reveals.contains(&did);
2330            let def = if info.uif || (info.hide && !revealed && proven_externally.is_none()) {
2331                self.fun_decl_to_fixpoint(did, scx)
2332            } else {
2333                self.fun_def_to_fixpoint(did, scx)?
2334            };
2335            defs.push((info.rank, def));
2336        }
2337
2338        // we sort by rank so the definitions go out without any forward dependencies.
2339        let defs = defs
2340            .into_iter()
2341            .sorted_by_key(|(rank, _)| *rank)
2342            .map(|(_, def)| def)
2343            .collect();
2344
2345        Ok(defs)
2346    }
2347
2348    fn fun_decl_to_fixpoint(
2349        &mut self,
2350        def_id: FluxDefId,
2351        scx: &mut SortEncodingCtxt,
2352    ) -> fixpoint::FunDef {
2353        let name = self.const_env.fun_decl_map[&def_id];
2354        let sort = scx.func_sort_to_fixpoint(&self.genv.func_sort(def_id));
2355        fixpoint::FunDef { name, sort, body: None, comment: Some(format!("flux def: {def_id:?}")) }
2356    }
2357
2358    pub fn fun_def_to_fixpoint(
2359        &mut self,
2360        def_id: FluxDefId,
2361        scx: &mut SortEncodingCtxt,
2362    ) -> QueryResult<fixpoint::FunDef> {
2363        let name = *self.const_env.fun_decl_map.get(&def_id).unwrap();
2364        let body = self.genv.inlined_body(def_id);
2365        let output = scx.sort_to_fixpoint(self.genv.func_sort(def_id).expect_mono().output());
2366        let (args, expr) = self.body_to_fixpoint(&body, scx)?;
2367        let (args, inputs) = args.into_iter().unzip();
2368        Ok(fixpoint::FunDef {
2369            name,
2370            sort: fixpoint::FunSort { params: 0, inputs, output },
2371            body: Some(fixpoint::FunBody { args, expr }),
2372            comment: Some(format!("flux def: {def_id:?}")),
2373        })
2374    }
2375
2376    fn body_to_fixpoint(
2377        &mut self,
2378        body: &rty::Binder<rty::Expr>,
2379        scx: &mut SortEncodingCtxt,
2380    ) -> QueryResult<(Vec<(fixpoint::Var, fixpoint::Sort)>, fixpoint::Expr)> {
2381        self.local_var_env
2382            .push_layer_with_fresh_names(body.vars().len());
2383
2384        let expr = self.expr_to_fixpoint(body.as_ref().skip_binder(), scx)?;
2385
2386        let args: Vec<(fixpoint::Var, fixpoint::Sort)> =
2387            iter::zip(self.local_var_env.pop_layer(), body.vars())
2388                .map(|(name, var)| (name.into(), scx.sort_to_fixpoint(var.expect_sort())))
2389                .collect();
2390
2391        Ok((args, expr))
2392    }
2393
2394    fn qualifier_to_fixpoint(
2395        &mut self,
2396        qualifier: &rty::Qualifier,
2397        scx: &mut SortEncodingCtxt,
2398    ) -> QueryResult<fixpoint::Qualifier> {
2399        let (args, body) = self.body_to_fixpoint(&qualifier.body, scx)?;
2400        let name = qualifier.def_id.name().to_string();
2401        Ok(fixpoint::Qualifier { name, args, body })
2402    }
2403}
2404
2405fn mk_implies(assumption: fixpoint::Pred, cstr: fixpoint::Constraint) -> fixpoint::Constraint {
2406    fixpoint::Constraint::ForAll(
2407        fixpoint::Bind {
2408            name: fixpoint::Var::Underscore,
2409            sort: fixpoint::Sort::Int,
2410            pred: assumption,
2411        },
2412        Box::new(cstr),
2413    )
2414}
2415
2416fn parse_kvid(kvid: &str) -> fixpoint::KVid {
2417    if kvid.starts_with("k")
2418        && let Some(kvid) = kvid[1..].parse::<u32>().ok()
2419    {
2420        fixpoint::KVid::from_u32(kvid)
2421    } else {
2422        tracked_span_bug!("unexpected kvar name {kvid}")
2423    }
2424}
2425
2426fn parse_local_var(name: &str) -> Option<fixpoint::Var> {
2427    if let Some(rest) = name.strip_prefix('a')
2428        && let Ok(idx) = rest.parse::<u32>()
2429    {
2430        return Some(fixpoint::Var::Local(fixpoint::LocalVar::from(idx)));
2431    }
2432    None
2433}
2434
2435fn parse_global_var(
2436    name: &str,
2437    fun_decl_map: &HashMap<usize, FluxDefId>,
2438    const_decl_map: &HashMap<usize, DefId>,
2439) -> Option<fixpoint::Var> {
2440    if let Some(rest) = name.strip_prefix('c')
2441        && let Ok(idx) = rest.parse::<u32>()
2442    {
2443        return Some(fixpoint::Var::Const(
2444            fixpoint::GlobalVar::from(idx),
2445            const_decl_map.get(&(idx as usize)).copied(),
2446        ));
2447    }
2448    // try parsing as a named global variable
2449    if let Some(rest) = name.strip_prefix("f$")
2450        && let parts = rest.split('$').collect::<Vec<_>>()
2451        && parts.len() == 2
2452        && let Ok(global_idx) = parts[1].parse::<u32>()
2453        && let Some(def_id) = fun_decl_map.get(&(global_idx as usize)).copied()
2454    {
2455        return Some(fixpoint::Var::Global(fixpoint::GlobalVar::from(global_idx), def_id));
2456    }
2457    None
2458}
2459
2460fn parse_param(name: &str) -> Option<fixpoint::Var> {
2461    if let Some(rest) = name.strip_prefix("reftgen$")
2462        && let parts = rest.split('$').collect::<Vec<_>>()
2463        && parts.len() == 2
2464        && let Ok(index) = parts[1].parse::<u32>()
2465    {
2466        let name = Symbol::intern(parts[0]);
2467        let param = EarlyReftParam { index, name };
2468        return Some(fixpoint::Var::Param(param));
2469    }
2470    None
2471}
2472
2473fn parse_data_proj(name: &str) -> Option<fixpoint::Var> {
2474    if let Some(rest) = name.strip_prefix("fld")
2475        && let parts = rest.split('$').collect::<Vec<_>>()
2476        && parts.len() == 2
2477        && let Ok(adt_id) = parts[0].parse::<u32>()
2478        && let Ok(field) = parts[1].parse::<u32>()
2479    {
2480        let adt_id = fixpoint::AdtId::from(adt_id);
2481        return Some(fixpoint::Var::DataProj { adt_id, field });
2482    }
2483    None
2484}
2485
2486fn parse_data_ctor(name: &str) -> Option<fixpoint::Var> {
2487    if let Some(rest) = name.strip_prefix("mkadt")
2488        && let parts = rest.split('$').collect::<Vec<_>>()
2489        && parts.len() == 2
2490        && let Ok(adt_id) = parts[0].parse::<u32>()
2491        && let Ok(variant_idx) = parts[1].parse::<u32>()
2492    {
2493        let adt_id = fixpoint::AdtId::from(adt_id);
2494        let variant_idx = VariantIdx::from(variant_idx);
2495        return Some(fixpoint::Var::DataCtor(adt_id, variant_idx));
2496    }
2497    None
2498}
2499
2500struct SexpParseCtxt<'a> {
2501    local_var_env: &'a mut LocalVarEnv,
2502    fun_decl_map: &'a HashMap<usize, FluxDefId>,
2503    const_decl_map: &'a HashMap<usize, DefId>,
2504}
2505
2506impl<'a> SexpParseCtxt<'a> {
2507    fn new(
2508        local_var_env: &'a mut LocalVarEnv,
2509        fun_decl_map: &'a HashMap<usize, FluxDefId>,
2510        const_decl_map: &'a HashMap<usize, DefId>,
2511    ) -> Self {
2512        Self { local_var_env, fun_decl_map, const_decl_map }
2513    }
2514}
2515
2516impl FromSexp<FixpointTypes> for SexpParseCtxt<'_> {
2517    fn fresh_var(&mut self) -> fixpoint::Var {
2518        fixpoint::Var::Local(self.local_var_env.fresh_name())
2519    }
2520
2521    fn kvar(&self, name: &str) -> Result<fixpoint::KVid, ParseError> {
2522        bug!("TODO: SexpParse: kvar: {name}")
2523    }
2524
2525    fn string(&self, s: &str) -> Result<fixpoint::SymStr, ParseError> {
2526        Ok(fixpoint::SymStr(Symbol::intern(s)))
2527    }
2528
2529    fn var(&self, name: &str) -> Result<fixpoint::Var, ParseError> {
2530        if let Some(var) = parse_local_var(name) {
2531            return Ok(var);
2532        }
2533        if let Some(var) = parse_global_var(name, self.fun_decl_map, self.const_decl_map) {
2534            return Ok(var);
2535        }
2536        if let Some(var) = parse_param(name) {
2537            return Ok(var);
2538        }
2539        if let Some(var) = parse_data_proj(name) {
2540            return Ok(var);
2541        }
2542        if let Some(var) = parse_data_ctor(name) {
2543            return Ok(var);
2544        }
2545        Err(ParseError::err(format!("Unknown variable: {name}")))
2546    }
2547
2548    fn sort(&self, name: &str) -> Result<fixpoint::DataSort, ParseError> {
2549        if let Some(idx) = name.strip_prefix("Adt")
2550            && let Ok(adt_id) = idx.parse::<u32>()
2551        {
2552            return Ok(fixpoint::DataSort::Adt(fixpoint::AdtId::from(adt_id)));
2553        }
2554        if let Some(idx) = name.strip_prefix("OpaqueAdt")
2555            && let Ok(opaque_id) = idx.parse::<u32>()
2556        {
2557            return Ok(fixpoint::DataSort::User(fixpoint::OpaqueId::from(opaque_id)));
2558        }
2559
2560        Err(ParseError::err(format!("Unknown sort: {name}")))
2561    }
2562}