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