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 opaque = self
844            .ecx
845            .const_env
846            .const_map
847            .iter()
848            .filter_map(|(key, decl)| {
849                if let ConstKey::PrimOp(op) = key { Some((decl.clone(), op.clone())) } else { None }
850            })
851            .collect();
852        let const_deps = ConstDeps { interpreted, opaque };
853        (const_deps, cstr)
854    }
855
856    pub fn generate_lean_files(
857        self,
858        def_id: MaybeExternId,
859        task: fixpoint::Task,
860        kvar_solutions: KVarSolutions,
861    ) -> QueryResult {
862        // FIXME(nilehmann) opaque sorts should be part of the task.
863        let opaque_sorts = self.scx.opaque_sorts_to_fixpoint(self.genv);
864        let (const_deps, constraint) = self.compute_const_deps(task.constants, task.constraint);
865        let sort_deps =
866            SortDeps { opaque_sorts, data_decls: task.data_decls, adt_map: self.scx.adt_sorts };
867
868        LeanEncoder::encode(
869            self.genv,
870            def_id,
871            self.ecx.local_var_env.pretty_var_map,
872            sort_deps,
873            task.define_funs,
874            const_deps,
875            task.kvars,
876            constraint,
877            kvar_solutions,
878        )
879        .map_err(|err| query_bug!("could not encode constraint: {err:?}"))
880    }
881
882    fn run_task_with_cache(
883        genv: GlobalEnv,
884        task: &fixpoint::Task,
885        def_id: DefId,
886        kind: FixpointQueryKind,
887        cache: &mut FixQueryCache,
888    ) -> VerificationResult<TagIdx> {
889        let key = kind.task_key(genv.tcx(), def_id);
890
891        let hash = task.hash_with_default();
892
893        if config::is_cache_enabled()
894            && let Some(result) = cache.lookup(&key, hash)
895        {
896            metrics::incr_metric_if(kind.is_body(), Metric::FnCached);
897            return result.clone();
898        }
899        let result = metrics::time_it(TimingKind::FixpointQuery(def_id, kind), || {
900            task.run()
901                .unwrap_or_else(|err| tracked_span_bug!("failed to run fixpoint: {err}"))
902        });
903
904        if config::is_cache_enabled() {
905            cache.insert(key, hash, result.clone());
906        }
907
908        result
909    }
910
911    fn tag_idx(&mut self, tag: Tag) -> TagIdx
912    where
913        Tag: std::fmt::Debug,
914    {
915        *self.tags_inv.entry(tag).or_insert_with(|| {
916            let idx = self.tags.push(tag);
917            self.comments.push(format!("Tag {idx}: {tag:?}"));
918            idx
919        })
920    }
921
922    pub(crate) fn with_early_param(&mut self, param: &EarlyReftParam) {
923        self.ecx
924            .local_var_env
925            .pretty_var_map
926            .set(PrettyVar::Param(*param), Some(param.name));
927    }
928
929    pub(crate) fn with_name_map<R>(
930        &mut self,
931        name: rty::Name,
932        provenance: NameProvenance,
933        f: impl FnOnce(&mut Self, fixpoint::LocalVar) -> R,
934    ) -> R {
935        let fresh = self.ecx.local_var_env.insert_fvar_map(name, provenance);
936        let r = f(self, fresh);
937        self.ecx.local_var_env.remove_fvar_map(name);
938        r
939    }
940
941    pub(crate) fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
942        self.scx.sort_to_fixpoint(sort)
943    }
944
945    pub(crate) fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
946        self.ecx.var_to_fixpoint(var)
947    }
948
949    /// Encodes an expression in head position as a [`fixpoint::Constraint`] "peeling out"
950    /// implications and foralls.
951    ///
952    /// [`fixpoint::Constraint`]: liquid_fixpoint::Constraint
953    pub(crate) fn head_to_fixpoint(
954        &mut self,
955        expr: &rty::Expr,
956        mk_tag: impl Fn(Option<ESpan>) -> Tag + Copy,
957    ) -> QueryResult<fixpoint::Constraint>
958    where
959        Tag: std::fmt::Debug,
960    {
961        match expr.kind() {
962            rty::ExprKind::BinaryOp(rty::BinOp::And, ..) => {
963                // avoid creating nested conjunctions
964                let cstrs = expr
965                    .flatten_conjs()
966                    .into_iter()
967                    .map(|e| self.head_to_fixpoint(e, mk_tag))
968                    .try_collect()?;
969                Ok(fixpoint::Constraint::conj(cstrs))
970            }
971            rty::ExprKind::BinaryOp(rty::BinOp::Imp, e1, e2) => {
972                let (bindings, assumption) = self.assumption_to_fixpoint(e1)?;
973                let cstr = self.head_to_fixpoint(e2, mk_tag)?;
974                Ok(fixpoint::Constraint::foralls(bindings, mk_implies(assumption, cstr)))
975            }
976            rty::ExprKind::KVar(kvar) => {
977                let mut bindings = vec![];
978                let pred = self.kvar_to_fixpoint(kvar, &mut bindings)?;
979                Ok(fixpoint::Constraint::foralls(bindings, fixpoint::Constraint::Pred(pred, None)))
980            }
981            rty::ExprKind::ForAll(pred) => {
982                self.ecx
983                    .local_var_env
984                    .push_layer_with_fresh_names(pred.vars().len());
985                let cstr = self.head_to_fixpoint(pred.as_ref().skip_binder(), mk_tag)?;
986                let vars = self.ecx.local_var_env.pop_layer();
987
988                let bindings = iter::zip(vars, pred.vars())
989                    .map(|(var, kind)| {
990                        fixpoint::Bind {
991                            name: var.into(),
992                            sort: self.scx.sort_to_fixpoint(kind.expect_sort()),
993                            pred: fixpoint::Pred::TRUE,
994                        }
995                    })
996                    .collect_vec();
997
998                Ok(fixpoint::Constraint::foralls(bindings, cstr))
999            }
1000            _ => {
1001                let tag_idx = self.tag_idx(mk_tag(expr.span()));
1002                let pred = fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?);
1003                Ok(fixpoint::Constraint::Pred(pred, Some(tag_idx)))
1004            }
1005        }
1006    }
1007
1008    /// Encodes an expression in assumptive position as a [`fixpoint::Pred`]. Returns the encoded
1009    /// predicate and a list of bindings produced by ANF-ing kvars.
1010    ///
1011    /// [`fixpoint::Pred`]: liquid_fixpoint::Pred
1012    pub(crate) fn assumption_to_fixpoint(
1013        &mut self,
1014        pred: &rty::Expr,
1015    ) -> QueryResult<(Vec<fixpoint::Bind>, fixpoint::Pred)> {
1016        let mut bindings = vec![];
1017        let mut preds = vec![];
1018        self.assumption_to_fixpoint_aux(pred, &mut bindings, &mut preds)?;
1019        Ok((bindings, fixpoint::Pred::and(preds)))
1020    }
1021
1022    /// Auxiliary function to merge nested conjunctions in a single predicate
1023    fn assumption_to_fixpoint_aux(
1024        &mut self,
1025        expr: &rty::Expr,
1026        bindings: &mut Vec<fixpoint::Bind>,
1027        preds: &mut Vec<fixpoint::Pred>,
1028    ) -> QueryResult {
1029        match expr.kind() {
1030            rty::ExprKind::BinaryOp(rty::BinOp::And, e1, e2) => {
1031                self.assumption_to_fixpoint_aux(e1, bindings, preds)?;
1032                self.assumption_to_fixpoint_aux(e2, bindings, preds)?;
1033            }
1034            rty::ExprKind::KVar(kvar) => {
1035                preds.push(self.kvar_to_fixpoint(kvar, bindings)?);
1036            }
1037            rty::ExprKind::ForAll(_) => {
1038                // If a forall appears in assumptive position replace it with true. This is sound
1039                // because we are weakening the context, i.e., anything true without the assumption
1040                // should remain true after adding it. Note that this relies on the predicate
1041                // appearing negatively. This is guaranteed by the surface syntax because foralls
1042                // can only appear at the top-level in a requires clause.
1043                preds.push(fixpoint::Pred::TRUE);
1044            }
1045            _ => {
1046                preds.push(fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?));
1047            }
1048        }
1049        Ok(())
1050    }
1051
1052    fn kvar_to_fixpoint(
1053        &mut self,
1054        kvar: &rty::KVar,
1055        bindings: &mut Vec<fixpoint::Bind>,
1056    ) -> QueryResult<fixpoint::Pred> {
1057        let decl = self.kvars.get(kvar.kvid);
1058        let kvids = self.kcx.declare(kvar.kvid, decl, &self.ecx.backend);
1059
1060        let all_args = self.ecx.exprs_to_fixpoint(&kvar.args, &mut self.scx)?;
1061
1062        // Fixpoint doesn't support kvars without arguments, which we do generate sometimes. To get
1063        // around it, we encode `$k()` as ($k 0), or more precisely `(forall ((x int) (= x 0)) ... ($k x)`
1064        // after ANF-ing.
1065        if all_args.is_empty() {
1066            let fresh = self.ecx.local_var_env.fresh_name();
1067            let var = fixpoint::Var::Local(fresh);
1068            bindings.push(fixpoint::Bind {
1069                name: fresh.into(),
1070                sort: fixpoint::Sort::Int,
1071                pred: fixpoint::Pred::Expr(fixpoint::Expr::eq(
1072                    fixpoint::Expr::Var(var),
1073                    fixpoint::Expr::int(0),
1074                )),
1075            });
1076            return Ok(fixpoint::Pred::KVar(kvids.start, vec![fixpoint::Expr::Var(var)]));
1077        }
1078
1079        let kvars = kvids
1080            .enumerate()
1081            .map(|(i, kvid)| {
1082                let args = all_args[i..].to_vec();
1083                fixpoint::Pred::KVar(kvid, args)
1084            })
1085            .collect_vec();
1086
1087        Ok(fixpoint::Pred::And(kvars))
1088    }
1089}
1090
1091fn const_to_fixpoint(cst: rty::Constant) -> fixpoint::Expr {
1092    match cst {
1093        rty::Constant::Int(i) => {
1094            if i.is_negative() {
1095                fixpoint::Expr::Neg(Box::new(fixpoint::Constant::Numeral(i.abs()).into()))
1096            } else {
1097                fixpoint::Constant::Numeral(i.abs()).into()
1098            }
1099        }
1100        rty::Constant::Real(r) => fixpoint::Constant::Real(r.0).into(),
1101        rty::Constant::Bool(b) => fixpoint::Constant::Boolean(b).into(),
1102        rty::Constant::Char(c) => fixpoint::Constant::Numeral(u128::from(c)).into(),
1103        rty::Constant::Str(s) => fixpoint::Constant::String(fixpoint::SymStr(s)).into(),
1104        rty::Constant::BitVec(i, size) => fixpoint::Constant::BitVec(i, size).into(),
1105    }
1106}
1107
1108/// During encoding into fixpoint we generate multiple fixpoint kvars per kvar in flux. A
1109/// [`KVarEncodingCtxt`] is used to keep track of the state needed for this.
1110///
1111/// See [`KVarEncoding`]
1112#[derive(Default)]
1113struct KVarEncodingCtxt {
1114    /// A map from a [`rty::KVid`] to the range of [`fixpoint::KVid`]s that will be used to
1115    /// encode it.
1116    ranges: FxIndexMap<rty::KVid, Range<fixpoint::KVid>>,
1117}
1118
1119impl KVarEncodingCtxt {
1120    /// Declares that a kvar has to be encoded into fixpoint and assigns a range of
1121    /// [`fixpoint::KVid`]'s to it.
1122    fn declare(
1123        &mut self,
1124        kvid: rty::KVid,
1125        decl: &KVarDecl,
1126        backend: &Backend,
1127    ) -> Range<fixpoint::KVid> {
1128        // The start of the next range
1129        let start = self
1130            .ranges
1131            .last()
1132            .map_or(fixpoint::KVid::from_u32(0), |(_, r)| r.end);
1133
1134        self.ranges
1135            .entry(kvid)
1136            .or_insert_with(|| {
1137                let single_encoding = matches!(decl.encoding, KVarEncoding::Single)
1138                    || matches!(backend, Backend::Lean);
1139                if single_encoding {
1140                    start..start + 1
1141                } else {
1142                    let n = usize::max(decl.self_args, 1);
1143                    start..start + n
1144                }
1145            })
1146            .clone()
1147    }
1148
1149    fn encode_kvars(&self, kvars: &KVarGen, scx: &mut SortEncodingCtxt) -> Vec<fixpoint::KVarDecl> {
1150        self.ranges
1151            .iter()
1152            .flat_map(|(orig, range)| {
1153                let mut all_sorts = kvars
1154                    .get(*orig)
1155                    .sorts
1156                    .iter()
1157                    .map(|s| scx.sort_to_fixpoint(s))
1158                    .collect_vec();
1159
1160                // See comment in `kvar_to_fixpoint`
1161                if all_sorts.is_empty() {
1162                    all_sorts = vec![fixpoint::Sort::Int];
1163                }
1164
1165                range.clone().enumerate().map(move |(i, kvid)| {
1166                    let sorts = all_sorts[i..].to_vec();
1167                    fixpoint::KVarDecl::new(kvid, sorts, format!("orig: {:?}", orig))
1168                })
1169            })
1170            .collect()
1171    }
1172
1173    /// For each [`rty::KVid`] `$k`, this function collects all predicates associated
1174    /// with the [`fixpoint::KVid`]s that encode `$k` and combines them into a single
1175    /// predicate by conjoining them.
1176    ///
1177    /// A group (i.e., a combined predicate) is included in the result only if *all*
1178    /// [`fixpoint::KVid`]s in the encoding range of `$k` are present in the input.
1179    fn group_kvar_solution(
1180        &self,
1181        mut items: Vec<(fixpoint::KVid, rty::Binder<rty::Expr>)>,
1182    ) -> FxIndexMap<rty::KVid, rty::Binder<rty::Expr>> {
1183        let mut map = FxIndexMap::default();
1184
1185        items.sort_by_key(|(kvid, _)| *kvid);
1186        items.reverse();
1187
1188        for (orig, range) in &self.ranges {
1189            let mut preds = vec![];
1190            while let Some((_, t)) = items.pop_if(|(k, _)| range.contains(k)) {
1191                preds.push(t);
1192            }
1193            // We only put it in the map if the entire range is present.
1194            if preds.len() == range.end.as_usize() - range.start.as_usize() {
1195                let vars = preds[0].vars().clone();
1196                let conj = rty::Expr::and_from_iter(
1197                    preds
1198                        .into_iter()
1199                        .enumerate()
1200                        .map(|(i, e)| e.skip_binder().shift_horizontally(i)),
1201                );
1202                map.insert(*orig, rty::Binder::bind_with_vars(conj, vars));
1203            }
1204        }
1205        map
1206    }
1207}
1208
1209/// Environment used to map from [`rty::Var`] to a [`fixpoint::LocalVar`].
1210struct LocalVarEnv {
1211    local_var_gen: IndexGen<fixpoint::LocalVar>,
1212    fvars: UnordMap<rty::Name, fixpoint::LocalVar>,
1213    /// Layers of late bound variables
1214    layers: Vec<Vec<fixpoint::LocalVar>>,
1215    /// While it might seem like the signature should be
1216    /// [`UnordMap<fixpoint::LocalVar, rty::Var>`], we encode the arguments to
1217    /// kvars (which can be arbitrary expressions) as local variables; thus we
1218    /// need to keep the output as an [`rty::Expr`] to reflect this.
1219    reverse_map: UnordMap<fixpoint::LocalVar, rty::Expr>,
1220    pretty_var_map: PrettyMap<fixpoint::LocalVar>,
1221}
1222
1223impl LocalVarEnv {
1224    fn new() -> Self {
1225        Self {
1226            local_var_gen: IndexGen::new(),
1227            fvars: Default::default(),
1228            layers: Vec::new(),
1229            reverse_map: Default::default(),
1230            pretty_var_map: PrettyMap::new(),
1231        }
1232    }
1233
1234    // This doesn't require to be mutable because `IndexGen` uses atomics, but we make it mutable
1235    // to better declare the intent.
1236    fn fresh_name(&mut self) -> fixpoint::LocalVar {
1237        self.local_var_gen.fresh()
1238    }
1239
1240    fn insert_fvar_map(
1241        &mut self,
1242        name: rty::Name,
1243        provenance: NameProvenance,
1244    ) -> fixpoint::LocalVar {
1245        let fresh = self.fresh_name();
1246        self.fvars.insert(name, fresh);
1247        self.reverse_map.insert(fresh, rty::Expr::fvar(name));
1248        self.pretty_var_map
1249            .set(PrettyVar::Local(fresh), provenance.opt_symbol());
1250        fresh
1251    }
1252
1253    fn remove_fvar_map(&mut self, name: rty::Name) {
1254        self.fvars.remove(&name);
1255    }
1256
1257    /// Push a layer of bound variables assigning a fresh [`fixpoint::LocalVar`] to each one
1258    fn push_layer_with_fresh_names(&mut self, count: usize) {
1259        let layer = (0..count).map(|_| self.fresh_name()).collect();
1260        self.layers.push(layer);
1261        // FIXME: (ck) what to put in reverse_map here?
1262    }
1263
1264    fn push_layer(&mut self, layer: Vec<fixpoint::LocalVar>) {
1265        self.layers.push(layer);
1266    }
1267
1268    fn pop_layer(&mut self) -> Vec<fixpoint::LocalVar> {
1269        self.layers.pop().unwrap()
1270    }
1271
1272    fn get_fvar(&self, name: rty::Name) -> Option<fixpoint::LocalVar> {
1273        self.fvars.get(&name).copied()
1274    }
1275
1276    fn get_late_bvar(&self, debruijn: DebruijnIndex, var: BoundVar) -> Option<fixpoint::LocalVar> {
1277        let depth = self.layers.len().checked_sub(debruijn.as_usize() + 1)?;
1278        self.layers[depth].get(var.as_usize()).copied()
1279    }
1280}
1281
1282pub struct KVarGen {
1283    kvars: IndexVec<rty::KVid, KVarDecl>,
1284    /// If true, generate dummy [holes] instead of kvars. Used during shape mode to avoid generating
1285    /// unnecessary kvars.
1286    ///
1287    /// [holes]: rty::ExprKind::Hole
1288    dummy: bool,
1289}
1290
1291impl KVarGen {
1292    pub(crate) fn new(dummy: bool) -> Self {
1293        Self { kvars: IndexVec::new(), dummy }
1294    }
1295
1296    fn get(&self, kvid: rty::KVid) -> &KVarDecl {
1297        &self.kvars[kvid]
1298    }
1299
1300    /// Generate a fresh [kvar] under several layers of [binders]. Each layer may contain any kind
1301    /// of bound variable, but variables that are not of kind [`BoundVariableKind::Refine`] will
1302    /// be filtered out.
1303    ///
1304    /// The variables bound in the last layer (last element of the `binders` slice) is expected to
1305    /// have only [`BoundVariableKind::Refine`] and all its elements are used as the [self arguments].
1306    /// The rest of the binders are appended to the `scope`.
1307    ///
1308    /// Note that the returned expression will have escaping variables and it is up to the caller to
1309    /// put it under an appropriate number of binders.
1310    ///
1311    /// Prefer using [`InferCtxt::fresh_kvar`] when possible.
1312    ///
1313    /// [binders]: rty::Binder
1314    /// [kvar]: rty::KVar
1315    /// [`InferCtxt::fresh_kvar`]: crate::infer::InferCtxt::fresh_kvar
1316    /// [self arguments]: rty::KVar::self_args
1317    /// [`BoundVariableKind::Refine`]: rty::BoundVariableKind::Refine
1318    pub fn fresh(
1319        &mut self,
1320        binders: &[rty::BoundVariableKinds],
1321        scope: impl IntoIterator<Item = (rty::Var, rty::Sort)>,
1322        encoding: KVarEncoding,
1323    ) -> rty::Expr {
1324        if self.dummy {
1325            return rty::Expr::hole(rty::HoleKind::Pred);
1326        }
1327
1328        let args = itertools::chain(
1329            binders.iter().rev().enumerate().flat_map(|(level, vars)| {
1330                let debruijn = DebruijnIndex::from_usize(level);
1331                vars.iter()
1332                    .cloned()
1333                    .enumerate()
1334                    .flat_map(move |(idx, var)| {
1335                        if let rty::BoundVariableKind::Refine(sort, _, kind) = var {
1336                            let br = rty::BoundReft { var: BoundVar::from_usize(idx), kind };
1337                            Some((rty::Var::Bound(debruijn, br), sort))
1338                        } else {
1339                            None
1340                        }
1341                    })
1342            }),
1343            scope,
1344        );
1345        let [.., last] = binders else {
1346            return self.fresh_inner(0, [], encoding);
1347        };
1348        let num_self_args = last
1349            .iter()
1350            .filter(|var| matches!(var, rty::BoundVariableKind::Refine(..)))
1351            .count();
1352        self.fresh_inner(num_self_args, args, encoding)
1353    }
1354
1355    fn fresh_inner<A>(&mut self, self_args: usize, args: A, encoding: KVarEncoding) -> rty::Expr
1356    where
1357        A: IntoIterator<Item = (rty::Var, rty::Sort)>,
1358    {
1359        // asset last one has things
1360        let mut sorts = vec![];
1361        let mut exprs = vec![];
1362
1363        let mut flattened_self_args = 0;
1364        for (i, (var, sort)) in args.into_iter().enumerate() {
1365            let is_self_arg = i < self_args;
1366            let var = var.to_expr();
1367            sort.walk(|sort, proj| {
1368                if !matches!(sort, rty::Sort::Loc) {
1369                    flattened_self_args += is_self_arg as usize;
1370                    sorts.push(sort.clone());
1371                    exprs.push(rty::Expr::field_projs(&var, proj));
1372                }
1373            });
1374        }
1375
1376        let kvid = self
1377            .kvars
1378            .push(KVarDecl { self_args: flattened_self_args, sorts, encoding });
1379
1380        let kvar = rty::KVar::new(kvid, flattened_self_args, exprs);
1381        rty::Expr::kvar(kvar)
1382    }
1383}
1384
1385#[derive(Clone)]
1386struct KVarDecl {
1387    self_args: usize,
1388    sorts: Vec<rty::Sort>,
1389    encoding: KVarEncoding,
1390}
1391
1392/// How an [`rty::KVar`] is encoded in the fixpoint constraint
1393#[derive(Clone, Copy)]
1394pub enum KVarEncoding {
1395    /// Generate a single kvar appending the self arguments and the scope, i.e.,
1396    /// a kvar `$k(a0, ...)[b0, ...]` becomes `$k(a0, ..., b0, ...)` in the fixpoint constraint.
1397    Single,
1398    /// Generate a conjunction of kvars, one per argument in [`rty::KVar::args`].
1399    /// Concretely, a kvar `$k(a0, a1, ..., an)[b0, ...]` becomes
1400    /// `$k0(a0, a1, ..., an, b0, ...) ∧ $k1(a1, ..., an, b0, ...) ∧ ... ∧ $kn(an, b0, ...)`
1401    Conj,
1402}
1403
1404impl std::fmt::Display for TagIdx {
1405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1406        write!(f, "{}", self.as_u32())
1407    }
1408}
1409
1410impl std::str::FromStr for TagIdx {
1411    type Err = std::num::ParseIntError;
1412
1413    fn from_str(s: &str) -> Result<Self, Self::Err> {
1414        Ok(Self::from_u32(s.parse()?))
1415    }
1416}
1417
1418#[derive(Default)]
1419struct ConstEnv<'tcx> {
1420    const_map: ConstMap<'tcx>,
1421    const_map_rev: HashMap<fixpoint::GlobalVar, ConstKey<'tcx>>,
1422    global_var_gen: IndexGen<fixpoint::GlobalVar>,
1423    fun_decl_map: FunDeclMap,
1424}
1425
1426impl<'tcx> ConstEnv<'tcx> {
1427    fn get_or_insert(
1428        &mut self,
1429        key: ConstKey<'tcx>,
1430        // make_name: impl FnOnce() -> fixpoint::GlobalVar,
1431        make_const_decl: impl FnOnce(fixpoint::GlobalVar) -> fixpoint::ConstDecl,
1432    ) -> &fixpoint::ConstDecl {
1433        self.const_map.entry(key.clone()).or_insert_with(|| {
1434            let global_name = self.global_var_gen.fresh();
1435            self.const_map_rev.insert(global_name, key);
1436            make_const_decl(global_name)
1437        })
1438    }
1439}
1440
1441pub struct ExprEncodingCtxt<'genv, 'tcx> {
1442    genv: GlobalEnv<'genv, 'tcx>,
1443    local_var_env: LocalVarEnv,
1444    const_env: ConstEnv<'tcx>,
1445    errors: Errors<'genv>,
1446    /// Id of the item being checked. This is a [`MaybeExternId`] because we may be encoding
1447    /// invariants for an extern spec on an enum.
1448    def_id: Option<MaybeExternId>,
1449    infcx: rustc_infer::infer::InferCtxt<'tcx>,
1450    backend: Backend,
1451}
1452
1453pub struct KVarSolutions {
1454    pub cut_solutions: FxIndexMap<fixpoint::KVid, ClosedSolution>,
1455    pub non_cut_solutions: FxIndexMap<fixpoint::KVid, ClosedSolution>,
1456}
1457
1458impl KVarSolutions {
1459    pub(crate) fn closed_solutions(
1460        variable_sorts: IndexMap<fixpoint::Var, fixpoint::Sort>,
1461        cut_solutions: FxIndexMap<fixpoint::KVid, FixpointSolution>,
1462        non_cut_solutions: FxIndexMap<fixpoint::KVid, FixpointSolution>,
1463    ) -> Self {
1464        let closed_cut_solutions = cut_solutions
1465            .into_iter()
1466            .map(|(k, v)| (k, (vec![], v)))
1467            .collect();
1468
1469        let mut closed_non_cut_solutions: FxIndexMap<fixpoint::KVid, ClosedSolution> =
1470            FxIndexMap::default();
1471        for (kvid, mut solution) in non_cut_solutions {
1472            let bound_vars: HashSet<&_> = solution.0.iter().map(|(var, _)| var).collect();
1473            let vars = solution.1.free_vars();
1474            let free_vars = vars.iter().filter(|var| {
1475                !bound_vars.contains(var)
1476                    && !matches!(
1477                        var,
1478                        fixpoint::Var::DataCtor(..)
1479                            | fixpoint::Var::Global(..)
1480                            | fixpoint::Var::DataProj { .. }
1481                    )
1482            });
1483            let free_var_subs = free_vars
1484                .map(|fvar| (*fvar, variable_sorts.get(fvar).unwrap().clone()))
1485                .collect();
1486            solution.1.var_sorts_to_int();
1487            closed_non_cut_solutions.insert(kvid, (free_var_subs, solution));
1488        }
1489        KVarSolutions {
1490            cut_solutions: closed_cut_solutions,
1491            non_cut_solutions: closed_non_cut_solutions,
1492        }
1493    }
1494
1495    pub(crate) fn is_empty(&self) -> bool {
1496        self.cut_solutions.is_empty() && self.non_cut_solutions.is_empty()
1497    }
1498}
1499
1500#[derive(Debug)]
1501pub struct SortDeps {
1502    pub opaque_sorts: Vec<(FluxDefId, fixpoint::SortDecl)>,
1503    pub data_decls: Vec<fixpoint::DataDecl>,
1504    pub adt_map: FxIndexSet<DefId>,
1505}
1506
1507pub struct ConstDeps {
1508    pub interpreted: Vec<InterpretedConst>,
1509    /// Primop constants: the decl paired with the `BinOp` that gives a stable, cross-run
1510    /// identity used to derive the Lean name.
1511    pub opaque: Vec<(fixpoint::ConstDecl, rty::BinOp)>,
1512}
1513
1514impl<'genv, 'tcx> ExprEncodingCtxt<'genv, 'tcx> {
1515    pub fn new(
1516        genv: GlobalEnv<'genv, 'tcx>,
1517        def_id: Option<MaybeExternId>,
1518        backend: Backend,
1519    ) -> Self {
1520        Self {
1521            genv,
1522            local_var_env: LocalVarEnv::new(),
1523            const_env: Default::default(),
1524            errors: Errors::new(genv.sess()),
1525            def_id,
1526            infcx: genv
1527                .tcx()
1528                .infer_ctxt()
1529                .with_next_trait_solver(true)
1530                .build(TypingMode::non_body_analysis()),
1531            backend,
1532        }
1533    }
1534
1535    fn def_span(&self) -> Span {
1536        self.def_id
1537            .map_or(DUMMY_SP, |def_id| self.genv.tcx().def_span(def_id))
1538    }
1539
1540    fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
1541        match var {
1542            rty::Var::Free(name) => {
1543                self.local_var_env
1544                    .get_fvar(*name)
1545                    .unwrap_or_else(|| {
1546                        span_bug!(self.def_span(), "no entry found for name: `{name:?}`")
1547                    })
1548                    .into()
1549            }
1550            rty::Var::Bound(debruijn, breft) => {
1551                self.local_var_env
1552                    .get_late_bvar(*debruijn, breft.var)
1553                    .unwrap_or_else(|| {
1554                        span_bug!(self.def_span(), "no entry found for late bound var: `{breft:?}`")
1555                    })
1556                    .into()
1557            }
1558            rty::Var::ConstGeneric(param) => fixpoint::Var::ConstGeneric(*param),
1559            rty::Var::EarlyParam(param) => fixpoint::Var::Param(*param),
1560            rty::Var::EVar(_) => {
1561                span_bug!(self.def_span(), "unexpected evar: `{var:?}`")
1562            }
1563        }
1564    }
1565
1566    fn variant_to_fixpoint(
1567        &self,
1568        scx: &mut SortEncodingCtxt,
1569        enum_def_id: &DefId,
1570        idx: VariantIdx,
1571    ) -> fixpoint::Var {
1572        let adt_id = scx.declare_adt(*enum_def_id);
1573        fixpoint::Var::DataCtor(adt_id, idx)
1574    }
1575
1576    fn struct_fields_to_fixpoint(
1577        &mut self,
1578        did: &DefId,
1579        flds: &[rty::Expr],
1580        scx: &mut SortEncodingCtxt,
1581    ) -> QueryResult<fixpoint::Expr> {
1582        // do not generate 1-tuples
1583        if let [fld] = flds {
1584            self.expr_to_fixpoint(fld, scx)
1585        } else {
1586            let adt_id = scx.declare_adt(*did);
1587            let ctor = fixpoint::Expr::Var(fixpoint::Var::DataCtor(adt_id, VariantIdx::ZERO));
1588            let args = flds
1589                .iter()
1590                .map(|fld| self.expr_to_fixpoint(fld, scx))
1591                .try_collect()?;
1592            Ok(fixpoint::Expr::App(Box::new(ctor), None, args, None))
1593        }
1594    }
1595
1596    fn fields_to_fixpoint(
1597        &mut self,
1598        flds: &[rty::Expr],
1599        scx: &mut SortEncodingCtxt,
1600    ) -> QueryResult<fixpoint::Expr> {
1601        // do not generate 1-tuples
1602        if let [fld] = flds {
1603            self.expr_to_fixpoint(fld, scx)
1604        } else {
1605            scx.declare_tuple(flds.len());
1606            let ctor = fixpoint::Expr::Var(fixpoint::Var::TupleCtor { arity: flds.len() });
1607            let args = flds
1608                .iter()
1609                .map(|fld| self.expr_to_fixpoint(fld, scx))
1610                .try_collect()?;
1611            Ok(fixpoint::Expr::App(Box::new(ctor), None, args, None))
1612        }
1613    }
1614
1615    fn internal_func_to_fixpoint(
1616        &mut self,
1617        internal_func: &InternalFuncKind,
1618        sort_args: &[rty::SortArg],
1619        args: &[rty::Expr],
1620        scx: &mut SortEncodingCtxt,
1621    ) -> QueryResult<fixpoint::Expr> {
1622        match internal_func {
1623            InternalFuncKind::Val(op) => {
1624                let func = fixpoint::Expr::Var(self.define_const_for_prim_op(op, scx));
1625                let args = self.exprs_to_fixpoint(args, scx)?;
1626                Ok(fixpoint::Expr::App(Box::new(func), None, args, None))
1627            }
1628            InternalFuncKind::Rel(op) => {
1629                let expr = if let Some(prim_rel) = self.genv.prim_rel_for(op)? {
1630                    prim_rel.body.replace_bound_refts(args)
1631                } else {
1632                    rty::Expr::tt()
1633                };
1634                self.expr_to_fixpoint(&expr, scx)
1635            }
1636            InternalFuncKind::Cast => {
1637                let [rty::SortArg::Sort(from), rty::SortArg::Sort(to)] = &sort_args else {
1638                    span_bug!(self.def_span(), "unexpected cast")
1639                };
1640                match from.cast_kind(to) {
1641                    rty::CastKind::Identity => self.expr_to_fixpoint(&args[0], scx),
1642                    rty::CastKind::BoolToInt => {
1643                        Ok(fixpoint::Expr::IfThenElse(Box::new([
1644                            self.expr_to_fixpoint(&args[0], scx)?,
1645                            fixpoint::Expr::int(1),
1646                            fixpoint::Expr::int(0),
1647                        ])))
1648                    }
1649                    rty::CastKind::IntoUnit => self.expr_to_fixpoint(&rty::Expr::unit(), scx),
1650                    rty::CastKind::Uninterpreted => {
1651                        let func = fixpoint::Expr::Var(self.define_const_for_cast(from, to, scx));
1652                        let args = self.exprs_to_fixpoint(args, scx)?;
1653                        Ok(fixpoint::Expr::App(Box::new(func), None, args, None))
1654                    }
1655                }
1656            }
1657            InternalFuncKind::PtrSize => {
1658                let func = fixpoint::Expr::Var(self.define_const_for_ptr_size(scx));
1659                let args = self.exprs_to_fixpoint(args, scx)?;
1660                Ok(fixpoint::Expr::App(Box::new(func), None, args, None))
1661            }
1662        }
1663    }
1664
1665    fn structurally_normalize_expr(&self, expr: &rty::Expr) -> QueryResult<rty::Expr> {
1666        if let Some(def_id) = self.def_id {
1667            structurally_normalize_expr(self.genv, def_id.resolved_id(), &self.infcx, expr)
1668        } else {
1669            Ok(expr.clone())
1670        }
1671    }
1672
1673    fn expr_to_fixpoint(
1674        &mut self,
1675        expr: &rty::Expr,
1676        scx: &mut SortEncodingCtxt,
1677    ) -> QueryResult<fixpoint::Expr> {
1678        let expr = self.structurally_normalize_expr(expr)?;
1679        let e = match expr.kind() {
1680            rty::ExprKind::Var(var) => fixpoint::Expr::Var(self.var_to_fixpoint(var)),
1681            rty::ExprKind::Constant(c) => const_to_fixpoint(*c),
1682            rty::ExprKind::BinaryOp(op, e1, e2) => self.bin_op_to_fixpoint(op, e1, e2, scx)?,
1683            rty::ExprKind::UnaryOp(op, e) => self.un_op_to_fixpoint(*op, e, scx)?,
1684            rty::ExprKind::FieldProj(e, proj) => self.proj_to_fixpoint(e, *proj, scx)?,
1685            rty::ExprKind::Tuple(flds) => self.fields_to_fixpoint(flds, scx)?,
1686            rty::ExprKind::Ctor(rty::Ctor::Struct(did), flds) => {
1687                self.struct_fields_to_fixpoint(did, flds, scx)?
1688            }
1689            rty::ExprKind::IsCtor(def_id, variant_idx, e) => {
1690                let ctor = self.variant_to_fixpoint(scx, def_id, *variant_idx);
1691                let e = self.expr_to_fixpoint(e, scx)?;
1692                fixpoint::Expr::IsCtor(ctor, Box::new(e))
1693            }
1694            rty::ExprKind::Ctor(rty::Ctor::Enum(did, idx), args) => {
1695                let ctor = self.variant_to_fixpoint(scx, did, *idx);
1696                let args = self.exprs_to_fixpoint(args, scx)?;
1697                fixpoint::Expr::App(Box::new(fixpoint::Expr::Var(ctor)), None, args, None)
1698            }
1699            rty::ExprKind::ConstDefId(did) => {
1700                let var = self.define_const_for_rust_const(*did, scx);
1701                fixpoint::Expr::Var(var)
1702            }
1703            rty::ExprKind::App(func, sort_args, args) => {
1704                if let rty::ExprKind::InternalFunc(func) = func.kind() {
1705                    self.internal_func_to_fixpoint(func, sort_args, args, scx)?
1706                } else {
1707                    let func = self.expr_to_fixpoint(func, scx)?;
1708                    let sort_args = self.sort_args_to_fixpoint(sort_args, scx);
1709                    let args = self.exprs_to_fixpoint(args, scx)?;
1710                    fixpoint::Expr::App(Box::new(func), Some(sort_args), args, None)
1711                }
1712            }
1713            rty::ExprKind::IfThenElse(p, e1, e2) => {
1714                fixpoint::Expr::IfThenElse(Box::new([
1715                    self.expr_to_fixpoint(p, scx)?,
1716                    self.expr_to_fixpoint(e1, scx)?,
1717                    self.expr_to_fixpoint(e2, scx)?,
1718                ]))
1719            }
1720            rty::ExprKind::Alias(alias_reft, args) => {
1721                let sort = self.genv.sort_of_assoc_reft(alias_reft.assoc_id)?;
1722                let sort = sort.instantiate_identity();
1723                let func =
1724                    fixpoint::Expr::Var(self.define_const_for_alias_reft(alias_reft, sort, scx));
1725                let args = args
1726                    .iter()
1727                    .map(|expr| self.expr_to_fixpoint(expr, scx))
1728                    .try_collect()?;
1729                fixpoint::Expr::App(Box::new(func), None, args, None)
1730            }
1731            rty::ExprKind::Abs(lam) => {
1732                let var = self.define_const_for_lambda(lam, scx);
1733                fixpoint::Expr::Var(var)
1734            }
1735            rty::ExprKind::Let(init, body) => {
1736                debug_assert_eq!(body.vars().len(), 1);
1737                let init = self.expr_to_fixpoint(init, scx)?;
1738
1739                self.local_var_env.push_layer_with_fresh_names(1);
1740                let body = self.expr_to_fixpoint(body.skip_binder_ref(), scx)?;
1741                let vars = self.local_var_env.pop_layer();
1742
1743                fixpoint::Expr::Let(vars[0].into(), Box::new([init, body]))
1744            }
1745            rty::ExprKind::GlobalFunc(SpecFuncKind::Thy(itf)) => fixpoint::Expr::ThyFunc(*itf),
1746            rty::ExprKind::GlobalFunc(SpecFuncKind::Def(def_id)) => {
1747                fixpoint::Expr::Var(self.declare_fun(*def_id))
1748            }
1749            rty::ExprKind::Exists(expr) => {
1750                let expr = self.body_to_fixpoint(expr, scx)?;
1751                fixpoint::Expr::Exists(expr.0, Box::new(expr.1))
1752            }
1753            rty::ExprKind::Hole(..)
1754            | rty::ExprKind::KVar(_)
1755            | rty::ExprKind::Local(_)
1756            | rty::ExprKind::PathProj(..)
1757            | rty::ExprKind::ForAll(_)
1758            | rty::ExprKind::InternalFunc(_) => {
1759                span_bug!(self.def_span(), "unexpected expr: `{expr:?}`")
1760            }
1761            rty::ExprKind::BoundedQuant(kind, rng, body) => {
1762                let exprs = (rng.start..rng.end).map(|i| {
1763                    let arg = rty::Expr::constant(rty::Constant::from(i));
1764                    body.replace_bound_reft(&arg)
1765                });
1766                let expr = match kind {
1767                    flux_middle::fhir::QuantKind::Forall => rty::Expr::and_from_iter(exprs),
1768                    flux_middle::fhir::QuantKind::Exists => rty::Expr::or_from_iter(exprs),
1769                };
1770                self.expr_to_fixpoint(&expr, scx)?
1771            }
1772        };
1773        Ok(e)
1774    }
1775
1776    fn sort_args_to_fixpoint(
1777        &mut self,
1778        sort_args: &[rty::SortArg],
1779        scx: &mut SortEncodingCtxt,
1780    ) -> Vec<fixpoint::Sort> {
1781        sort_args
1782            .iter()
1783            .map(|s_arg| self.sort_arg_to_fixpoint(s_arg, scx))
1784            .collect()
1785    }
1786
1787    fn sort_arg_to_fixpoint(
1788        &mut self,
1789        sort_arg: &rty::SortArg,
1790        scx: &mut SortEncodingCtxt,
1791    ) -> fixpoint::Sort {
1792        match sort_arg {
1793            rty::SortArg::Sort(sort) => scx.sort_to_fixpoint(sort),
1794            rty::SortArg::BvSize(sz) => bv_size_to_fixpoint(*sz),
1795        }
1796    }
1797
1798    fn exprs_to_fixpoint<'b>(
1799        &mut self,
1800        exprs: impl IntoIterator<Item = &'b rty::Expr>,
1801        scx: &mut SortEncodingCtxt,
1802    ) -> QueryResult<Vec<fixpoint::Expr>> {
1803        exprs
1804            .into_iter()
1805            .map(|e| self.expr_to_fixpoint(e, scx))
1806            .try_collect()
1807    }
1808
1809    fn proj_to_fixpoint(
1810        &mut self,
1811        e: &rty::Expr,
1812        proj: rty::FieldProj,
1813        scx: &mut SortEncodingCtxt,
1814    ) -> QueryResult<fixpoint::Expr> {
1815        let arity = proj.arity(self.genv)?;
1816        // we encode 1-tuples as the single element inside so no projection necessary here
1817        if arity == 1 {
1818            self.expr_to_fixpoint(e, scx)
1819        } else {
1820            let proj = match proj {
1821                rty::FieldProj::Tuple { arity, field } => {
1822                    scx.declare_tuple(arity);
1823                    fixpoint::Var::TupleProj { arity, field }
1824                }
1825                rty::FieldProj::Adt { def_id, field } => {
1826                    let adt_id = scx.declare_adt(def_id);
1827                    fixpoint::Var::DataProj { adt_id, field }
1828                }
1829            };
1830            let proj = fixpoint::Expr::Var(proj);
1831            Ok(fixpoint::Expr::App(
1832                Box::new(proj),
1833                None,
1834                vec![self.expr_to_fixpoint(e, scx)?],
1835                None,
1836            ))
1837        }
1838    }
1839
1840    fn un_op_to_fixpoint(
1841        &mut self,
1842        op: rty::UnOp,
1843        e: &rty::Expr,
1844        scx: &mut SortEncodingCtxt,
1845    ) -> QueryResult<fixpoint::Expr> {
1846        match op {
1847            rty::UnOp::Not => Ok(fixpoint::Expr::Not(Box::new(self.expr_to_fixpoint(e, scx)?))),
1848            rty::UnOp::Neg => Ok(fixpoint::Expr::Neg(Box::new(self.expr_to_fixpoint(e, scx)?))),
1849        }
1850    }
1851
1852    fn bv_rel_to_fixpoint(&self, rel: &fixpoint::BinRel) -> fixpoint::Expr {
1853        let itf = match rel {
1854            fixpoint::BinRel::Gt => fixpoint::ThyFunc::BvUgt,
1855            fixpoint::BinRel::Ge => fixpoint::ThyFunc::BvUge,
1856            fixpoint::BinRel::Lt => fixpoint::ThyFunc::BvUlt,
1857            fixpoint::BinRel::Le => fixpoint::ThyFunc::BvUle,
1858            _ => span_bug!(self.def_span(), "not a bitvector relation!"),
1859        };
1860        fixpoint::Expr::ThyFunc(itf)
1861    }
1862
1863    fn set_op_to_fixpoint(&self, op: &rty::BinOp) -> fixpoint::Expr {
1864        let itf = match op {
1865            rty::BinOp::Sub(_) => fixpoint::ThyFunc::SetDif,
1866            rty::BinOp::BitAnd(_) => fixpoint::ThyFunc::SetCap,
1867            rty::BinOp::BitOr(_) => fixpoint::ThyFunc::SetCup,
1868            _ => span_bug!(self.def_span(), "not a set operation!"),
1869        };
1870        fixpoint::Expr::ThyFunc(itf)
1871    }
1872
1873    fn bv_op_to_fixpoint(&self, op: &rty::BinOp) -> fixpoint::Expr {
1874        let itf = match op {
1875            rty::BinOp::Add(_) => fixpoint::ThyFunc::BvAdd,
1876            rty::BinOp::Sub(_) => fixpoint::ThyFunc::BvSub,
1877            rty::BinOp::Mul(_) => fixpoint::ThyFunc::BvMul,
1878            rty::BinOp::Div(_) => fixpoint::ThyFunc::BvUdiv,
1879            rty::BinOp::Mod(_) => fixpoint::ThyFunc::BvUrem,
1880            rty::BinOp::BitAnd(_) => fixpoint::ThyFunc::BvAnd,
1881            rty::BinOp::BitOr(_) => fixpoint::ThyFunc::BvOr,
1882            rty::BinOp::BitXor(_) => fixpoint::ThyFunc::BvXor,
1883            rty::BinOp::BitShl(_) => fixpoint::ThyFunc::BvShl,
1884            rty::BinOp::BitShr(_) => fixpoint::ThyFunc::BvLshr,
1885            _ => span_bug!(self.def_span(), "not a bitvector operation!"),
1886        };
1887        fixpoint::Expr::ThyFunc(itf)
1888    }
1889
1890    fn bin_op_to_fixpoint(
1891        &mut self,
1892        op: &rty::BinOp,
1893        e1: &rty::Expr,
1894        e2: &rty::Expr,
1895        scx: &mut SortEncodingCtxt,
1896    ) -> QueryResult<fixpoint::Expr> {
1897        let op = match op {
1898            rty::BinOp::Eq => {
1899                return Ok(fixpoint::Expr::Atom(
1900                    fixpoint::BinRel::Eq,
1901                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1902                ));
1903            }
1904            rty::BinOp::Ne => {
1905                return Ok(fixpoint::Expr::Atom(
1906                    fixpoint::BinRel::Ne,
1907                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1908                ));
1909            }
1910            rty::BinOp::Gt(sort) => {
1911                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Gt, e1, e2, scx);
1912            }
1913            rty::BinOp::Ge(sort) => {
1914                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Ge, e1, e2, scx);
1915            }
1916            rty::BinOp::Lt(sort) => {
1917                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Lt, e1, e2, scx);
1918            }
1919            rty::BinOp::Le(sort) => {
1920                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Le, e1, e2, scx);
1921            }
1922            rty::BinOp::And => {
1923                return Ok(fixpoint::Expr::And(vec![
1924                    self.expr_to_fixpoint(e1, scx)?,
1925                    self.expr_to_fixpoint(e2, scx)?,
1926                ]));
1927            }
1928            rty::BinOp::Or => {
1929                return Ok(fixpoint::Expr::Or(vec![
1930                    self.expr_to_fixpoint(e1, scx)?,
1931                    self.expr_to_fixpoint(e2, scx)?,
1932                ]));
1933            }
1934            rty::BinOp::Imp => {
1935                return Ok(fixpoint::Expr::Imp(Box::new([
1936                    self.expr_to_fixpoint(e1, scx)?,
1937                    self.expr_to_fixpoint(e2, scx)?,
1938                ])));
1939            }
1940            rty::BinOp::Iff => {
1941                return Ok(fixpoint::Expr::Iff(Box::new([
1942                    self.expr_to_fixpoint(e1, scx)?,
1943                    self.expr_to_fixpoint(e2, scx)?,
1944                ])));
1945            }
1946
1947            // Bit vector operations
1948            rty::BinOp::Add(rty::Sort::BitVec(_))
1949            | rty::BinOp::Sub(rty::Sort::BitVec(_))
1950            | rty::BinOp::Mul(rty::Sort::BitVec(_))
1951            | rty::BinOp::Div(rty::Sort::BitVec(_))
1952            | rty::BinOp::Mod(rty::Sort::BitVec(_))
1953            | rty::BinOp::BitAnd(rty::Sort::BitVec(_))
1954            | rty::BinOp::BitOr(rty::Sort::BitVec(_))
1955            | rty::BinOp::BitXor(rty::Sort::BitVec(_))
1956            | rty::BinOp::BitShl(rty::Sort::BitVec(_))
1957            | rty::BinOp::BitShr(rty::Sort::BitVec(_)) => {
1958                let bv_func = self.bv_op_to_fixpoint(op);
1959                return Ok(fixpoint::Expr::App(
1960                    Box::new(bv_func),
1961                    None,
1962                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1963                    None,
1964                ));
1965            }
1966
1967            // Set operations
1968            rty::BinOp::Sub(rty::Sort::App(rty::SortCtor::Set, _))
1969            | rty::BinOp::BitAnd(rty::Sort::App(rty::SortCtor::Set, _))
1970            | rty::BinOp::BitOr(rty::Sort::App(rty::SortCtor::Set, _)) => {
1971                let set_func = self.set_op_to_fixpoint(op);
1972                return Ok(fixpoint::Expr::App(
1973                    Box::new(set_func),
1974                    None,
1975                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1976                    None,
1977                ));
1978            }
1979
1980            // Interpreted arithmetic operations
1981            rty::BinOp::Add(_) => fixpoint::BinOp::Add,
1982            rty::BinOp::Sub(_) => fixpoint::BinOp::Sub,
1983            rty::BinOp::Mul(_) => fixpoint::BinOp::Mul,
1984            rty::BinOp::Div(_) => fixpoint::BinOp::Div,
1985            rty::BinOp::Mod(_) => fixpoint::BinOp::Mod,
1986
1987            rty::BinOp::BitAnd(sort)
1988            | rty::BinOp::BitOr(sort)
1989            | rty::BinOp::BitXor(sort)
1990            | rty::BinOp::BitShl(sort)
1991            | rty::BinOp::BitShr(sort) => {
1992                bug!("unsupported operation `{op:?}` for sort `{sort:?}`");
1993            }
1994        };
1995        Ok(fixpoint::Expr::BinaryOp(
1996            op,
1997            Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1998        ))
1999    }
2000
2001    /// A binary relation is encoded as a structurally recursive relation between aggregate sorts.
2002    /// For "leaf" expressions, we encode them as an interpreted relation if the sort supports it,
2003    /// otherwise we use an uninterpreted function. For example, consider the following relation
2004    /// between two tuples of sort `(int, int -> int)`
2005    /// ```text
2006    /// (0, λv. v + 1) <= (1, λv. v + 1)
2007    /// ```
2008    /// The encoding in fixpoint will be
2009    ///
2010    /// ```text
2011    /// 0 <= 1 && (le (λv. v + 1) (λv. v + 1))
2012    /// ```
2013    /// Where `<=` is the (interpreted) less than or equal relation between integers and `le` is
2014    /// an uninterpreted relation between ([the encoding] of) lambdas.
2015    ///
2016    /// [the encoding]: Self::define_const_for_lambda
2017    fn bin_rel_to_fixpoint(
2018        &mut self,
2019        sort: &rty::Sort,
2020        rel: fixpoint::BinRel,
2021        e1: &rty::Expr,
2022        e2: &rty::Expr,
2023        scx: &mut SortEncodingCtxt,
2024    ) -> QueryResult<fixpoint::Expr> {
2025        let e = match sort {
2026            rty::Sort::Int | rty::Sort::Real | rty::Sort::Char => {
2027                fixpoint::Expr::Atom(
2028                    rel,
2029                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
2030                )
2031            }
2032            rty::Sort::BitVec(_) => {
2033                let e1 = self.expr_to_fixpoint(e1, scx)?;
2034                let e2 = self.expr_to_fixpoint(e2, scx)?;
2035                let rel = self.bv_rel_to_fixpoint(&rel);
2036                fixpoint::Expr::App(Box::new(rel), None, vec![e1, e2], None)
2037            }
2038            rty::Sort::Tuple(sorts) => {
2039                let arity = sorts.len();
2040                self.apply_bin_rel_rec(sorts, rel, e1, e2, scx, |field| {
2041                    rty::FieldProj::Tuple { arity, field }
2042                })?
2043            }
2044            rty::Sort::App(rty::SortCtor::Adt(sort_def), args)
2045                if let Some(variant) = sort_def.opt_struct_variant() =>
2046            {
2047                let def_id = sort_def.did();
2048                let sorts = variant.field_sorts(args);
2049                self.apply_bin_rel_rec(&sorts, rel, e1, e2, scx, |field| {
2050                    rty::FieldProj::Adt { def_id, field }
2051                })?
2052            }
2053            _ => {
2054                let rel = fixpoint::Expr::Var(fixpoint::Var::UIFRel(rel));
2055                fixpoint::Expr::App(
2056                    Box::new(rel),
2057                    None,
2058                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
2059                    None,
2060                )
2061            }
2062        };
2063        Ok(e)
2064    }
2065
2066    /// Apply binary relation recursively over aggregate expressions
2067    fn apply_bin_rel_rec(
2068        &mut self,
2069        sorts: &[rty::Sort],
2070        rel: fixpoint::BinRel,
2071        e1: &rty::Expr,
2072        e2: &rty::Expr,
2073        scx: &mut SortEncodingCtxt,
2074        mk_proj: impl Fn(u32) -> rty::FieldProj,
2075    ) -> QueryResult<fixpoint::Expr> {
2076        Ok(fixpoint::Expr::and(
2077            sorts
2078                .iter()
2079                .enumerate()
2080                .map(|(idx, s)| {
2081                    let proj = mk_proj(idx as u32);
2082                    let e1 = e1.proj_and_reduce(proj);
2083                    let e2 = e2.proj_and_reduce(proj);
2084                    self.bin_rel_to_fixpoint(s, rel, &e1, &e2, scx)
2085                })
2086                .try_collect()?,
2087        ))
2088    }
2089
2090    /// Declare that the `def_id` of a Flux function (potentially a UIF) needs to be
2091    /// encoded and assigns a name to it if it hasn't yet been declared. The encoding of the
2092    /// function body happens in [`Self::define_funs`].
2093    pub fn declare_fun(&mut self, def_id: FluxDefId) -> fixpoint::Var {
2094        *self
2095            .const_env
2096            .fun_decl_map
2097            .entry(def_id)
2098            .or_insert_with(|| {
2099                let id = self.const_env.global_var_gen.fresh();
2100                fixpoint::Var::Global(id, def_id)
2101            })
2102    }
2103
2104    /// The logic below is a bit "duplicated" with the `prim_op_sort` in `sortck.rs`;
2105    /// They are not exactly the same because this is on rty and the other one on fhir.
2106    /// We should make sure these two remain in sync.
2107    ///
2108    /// (NOTE:PrimOpSort) We are somewhat "overloading" the `BinOps`: as we are using them
2109    /// for (a) interpreted operations on bit vectors AND (b) uninterpreted functions on integers.
2110    /// So when Binop::BitShr (a) appears in a ExprKind::BinOp, it means bit vectors, but
2111    /// (b) inside ExprKind::InternalFunc it means int.
2112    fn prim_op_sort(op: &rty::BinOp, span: Span) -> rty::PolyFuncSort {
2113        match op {
2114            rty::BinOp::BitAnd(rty::Sort::Int)
2115            | rty::BinOp::BitOr(rty::Sort::Int)
2116            | rty::BinOp::BitXor(rty::Sort::Int)
2117            | rty::BinOp::BitShl(rty::Sort::Int)
2118            | rty::BinOp::BitShr(rty::Sort::Int) => {
2119                let fsort =
2120                    rty::FuncSort::new(vec![rty::Sort::Int, rty::Sort::Int], rty::Sort::Int);
2121                rty::PolyFuncSort::new(List::empty(), fsort)
2122            }
2123            _ => span_bug!(span, "unexpected prim op: {op:?} in `prim_op_sort`"),
2124        }
2125    }
2126
2127    fn define_const_for_cast(
2128        &mut self,
2129        from: &rty::Sort,
2130        to: &rty::Sort,
2131        scx: &mut SortEncodingCtxt,
2132    ) -> fixpoint::Var {
2133        let key = ConstKey::Cast(from.clone(), to.clone());
2134        self.const_env
2135            .get_or_insert(key, |global_name| {
2136                let fsort = rty::FuncSort::new(vec![from.clone()], to.clone());
2137                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
2138                let sort = scx.func_sort_to_fixpoint(&fsort).into_sort();
2139                fixpoint::ConstDecl {
2140                    name: fixpoint::Var::Const(global_name, None),
2141                    sort,
2142                    comment: Some(format!("cast uif: ({from:?}) -> {to:?}")),
2143                }
2144            })
2145            .name
2146    }
2147
2148    fn define_const_for_ptr_size(&mut self, scx: &mut SortEncodingCtxt) -> fixpoint::Var {
2149        let key = ConstKey::PtrSize;
2150        self.const_env
2151            .get_or_insert(key, |global_name| {
2152                let fsort = rty::FuncSort::new(vec![rty::Sort::RawPtr], rty::Sort::Int);
2153                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
2154                let sort = scx.func_sort_to_fixpoint(&fsort).into_sort();
2155                fixpoint::ConstDecl {
2156                    name: fixpoint::Var::Const(global_name, None),
2157                    sort,
2158                    comment: Some("ptr_size uif: RawPtr -> Int".to_string()),
2159                }
2160            })
2161            .name
2162    }
2163
2164    fn define_const_for_prim_op(
2165        &mut self,
2166        op: &rty::BinOp,
2167        scx: &mut SortEncodingCtxt,
2168    ) -> fixpoint::Var {
2169        let key = ConstKey::PrimOp(op.clone());
2170        let span = self.def_span();
2171        self.const_env
2172            .get_or_insert(key, |global_name| {
2173                let sort = scx
2174                    .func_sort_to_fixpoint(&Self::prim_op_sort(op, span))
2175                    .into_sort();
2176                fixpoint::ConstDecl {
2177                    name: fixpoint::Var::Const(global_name, None),
2178                    sort,
2179                    comment: Some(format!("prim op uif: {op:?}")),
2180                }
2181            })
2182            .name
2183    }
2184
2185    fn define_const_for_rust_const(
2186        &mut self,
2187        def_id: DefId,
2188        scx: &mut SortEncodingCtxt,
2189    ) -> fixpoint::Var {
2190        let key = ConstKey::RustConst(def_id);
2191        self.const_env
2192            .get_or_insert(key, |global_name| {
2193                let sort = self.genv.sort_of_def_id(def_id).unwrap().unwrap();
2194                fixpoint::ConstDecl {
2195                    name: fixpoint::Var::Const(global_name, Some(def_id)),
2196                    sort: scx.sort_to_fixpoint(&sort),
2197                    comment: Some(format!("rust const: {}", def_id_to_string(def_id))),
2198                }
2199            })
2200            .name
2201    }
2202
2203    /// returns the 'constant' UIF for Var used to represent the alias_pred, creating and adding it
2204    /// to the const_map if necessary
2205    fn define_const_for_alias_reft(
2206        &mut self,
2207        alias_reft: &rty::AliasReft,
2208        fsort: rty::FuncSort,
2209        scx: &mut SortEncodingCtxt,
2210    ) -> fixpoint::Var {
2211        let tcx = self.genv.tcx();
2212        let args = alias_reft
2213            .args
2214            .to_rustc(tcx)
2215            .truncate_to(tcx, tcx.generics_of(alias_reft.assoc_id.parent()));
2216        // See <https://github.com/flux-rs/flux/issues/1510#issuecomment-3953871782> as an example
2217        // for why we erase regions.
2218        let args = tcx.erase_and_anonymize_regions(args);
2219        let key = ConstKey::Alias(alias_reft.assoc_id, args);
2220        self.const_env
2221            .get_or_insert(key, |global_name| {
2222                let comment = Some(format!("alias reft: {alias_reft:?}"));
2223                let name = fixpoint::Var::Const(global_name, None);
2224                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
2225                let sort = scx.func_sort_to_fixpoint(&fsort).into_sort();
2226                fixpoint::ConstDecl { name, comment, sort }
2227            })
2228            .name
2229    }
2230
2231    /// We encode lambdas with uninterpreted constant. Two syntactically equal lambdas will be encoded
2232    /// with the same constant.
2233    fn define_const_for_lambda(
2234        &mut self,
2235        lam: &rty::Lambda,
2236        scx: &mut SortEncodingCtxt,
2237    ) -> fixpoint::Var {
2238        let key = ConstKey::Lambda(lam.clone());
2239        self.const_env
2240            .get_or_insert(key, |global_name| {
2241                let comment = Some(format!("lambda: {lam:?}"));
2242                let name = fixpoint::Var::Const(global_name, None);
2243                let sort = scx
2244                    .func_sort_to_fixpoint(&lam.fsort().to_poly())
2245                    .into_sort();
2246                fixpoint::ConstDecl { name, comment, sort }
2247            })
2248            .name
2249    }
2250
2251    fn assume_const_values(
2252        &mut self,
2253        mut constraint: fixpoint::Constraint,
2254        scx: &mut SortEncodingCtxt,
2255    ) -> QueryResult<fixpoint::Constraint> {
2256        // Encoding the value for a constant could in theory define more constants for which
2257        // we need to assume values, so we iterate until there are no more constants.
2258        let mut idx = 0;
2259        while let Some((key, const_)) = self.const_env.const_map.get_index(idx) {
2260            idx += 1;
2261
2262            match key {
2263                ConstKey::RustConst(def_id) => {
2264                    let info = self.genv.constant_info(def_id)?;
2265                    match info {
2266                        rty::ConstantInfo::Uninterpreted => {}
2267                        rty::ConstantInfo::Interpreted(val, _) => {
2268                            let const_name = const_.name;
2269                            let const_sort = const_.sort.clone();
2270
2271                            let e1 = fixpoint::Expr::Var(const_.name);
2272                            let e2 = self.expr_to_fixpoint(&val, scx)?;
2273                            let pred = fixpoint::Pred::Expr(e1.eq(e2));
2274
2275                            let bind = match self.backend {
2276                                Backend::Fixpoint => {
2277                                    fixpoint::Bind {
2278                                        name: fixpoint::Var::Underscore,
2279                                        sort: fixpoint::Sort::Int,
2280                                        pred,
2281                                    }
2282                                }
2283                                Backend::Lean => {
2284                                    fixpoint::Bind { name: const_name, sort: const_sort, pred }
2285                                }
2286                            };
2287                            constraint = fixpoint::Constraint::ForAll(bind, Box::new(constraint));
2288                        }
2289                    }
2290                }
2291                ConstKey::Alias(..) if matches!(self.backend, Backend::Lean) => {
2292                    constraint = fixpoint::Constraint::ForAll(
2293                        fixpoint::Bind {
2294                            name: const_.name,
2295                            sort: const_.sort.clone(),
2296                            pred: fixpoint::Pred::TRUE,
2297                        },
2298                        Box::new(constraint),
2299                    );
2300                }
2301                ConstKey::Alias(..)
2302                | ConstKey::Cast(..)
2303                | ConstKey::Lambda(..)
2304                | ConstKey::PrimOp(..)
2305                | ConstKey::PtrSize => {}
2306            }
2307        }
2308        Ok(constraint)
2309    }
2310
2311    fn qualifiers_for(
2312        &mut self,
2313        def_id: LocalDefId,
2314        scx: &mut SortEncodingCtxt,
2315    ) -> QueryResult<Vec<fixpoint::Qualifier>> {
2316        self.genv
2317            .qualifiers_for(def_id)?
2318            .map(|qual| self.qualifier_to_fixpoint(qual, scx))
2319            .try_collect()
2320    }
2321
2322    fn define_funs(
2323        &mut self,
2324        def_id: MaybeExternId,
2325        scx: &mut SortEncodingCtxt,
2326    ) -> QueryResult<Vec<fixpoint::FunDef>> {
2327        let reveals: UnordSet<FluxDefId> = self
2328            .genv
2329            .reveals_for(def_id.local_id())
2330            .iter()
2331            .copied()
2332            .collect();
2333        let proven_externally = self.genv.proven_externally(def_id.local_id());
2334        let mut defs = vec![];
2335
2336        // Iterate till encoding the body of functions doesn't require any more functions to be encoded.
2337        let mut idx = 0;
2338        while let Some((&did, _)) = self.const_env.fun_decl_map.get_index(idx) {
2339            idx += 1;
2340
2341            let info = self.genv.normalized_info(did);
2342            let revealed = reveals.contains(&did);
2343            let def = if info.uif || (info.hide && !revealed && proven_externally.is_none()) {
2344                self.fun_decl_to_fixpoint(did, scx)
2345            } else {
2346                self.fun_def_to_fixpoint(did, scx)?
2347            };
2348            defs.push((info.rank, def));
2349        }
2350
2351        // we sort by rank so the definitions go out without any forward dependencies.
2352        let defs = defs
2353            .into_iter()
2354            .sorted_by_key(|(rank, _)| *rank)
2355            .map(|(_, def)| def)
2356            .collect();
2357
2358        Ok(defs)
2359    }
2360
2361    fn fun_decl_to_fixpoint(
2362        &mut self,
2363        def_id: FluxDefId,
2364        scx: &mut SortEncodingCtxt,
2365    ) -> fixpoint::FunDef {
2366        let name = self.const_env.fun_decl_map[&def_id];
2367        let sort = scx.func_sort_to_fixpoint(&self.genv.func_sort(def_id));
2368        fixpoint::FunDef { name, sort, body: None, comment: Some(format!("flux def: {def_id:?}")) }
2369    }
2370
2371    pub fn fun_def_to_fixpoint(
2372        &mut self,
2373        def_id: FluxDefId,
2374        scx: &mut SortEncodingCtxt,
2375    ) -> QueryResult<fixpoint::FunDef> {
2376        let name = *self.const_env.fun_decl_map.get(&def_id).unwrap();
2377        let body = self.genv.inlined_body(def_id);
2378        let output = scx.sort_to_fixpoint(self.genv.func_sort(def_id).expect_mono().output());
2379        let (args, expr) = self.body_to_fixpoint(&body, scx)?;
2380        let (args, inputs) = args.into_iter().unzip();
2381        Ok(fixpoint::FunDef {
2382            name,
2383            sort: fixpoint::FunSort { params: 0, inputs, output },
2384            body: Some(fixpoint::FunBody { args, expr }),
2385            comment: Some(format!("flux def: {def_id:?}")),
2386        })
2387    }
2388
2389    fn body_to_fixpoint(
2390        &mut self,
2391        body: &rty::Binder<rty::Expr>,
2392        scx: &mut SortEncodingCtxt,
2393    ) -> QueryResult<(Vec<(fixpoint::Var, fixpoint::Sort)>, fixpoint::Expr)> {
2394        self.local_var_env
2395            .push_layer_with_fresh_names(body.vars().len());
2396
2397        let expr = self.expr_to_fixpoint(body.as_ref().skip_binder(), scx)?;
2398
2399        let args: Vec<(fixpoint::Var, fixpoint::Sort)> =
2400            iter::zip(self.local_var_env.pop_layer(), body.vars())
2401                .map(|(name, var)| (name.into(), scx.sort_to_fixpoint(var.expect_sort())))
2402                .collect();
2403
2404        Ok((args, expr))
2405    }
2406
2407    fn qualifier_to_fixpoint(
2408        &mut self,
2409        qualifier: &rty::Qualifier,
2410        scx: &mut SortEncodingCtxt,
2411    ) -> QueryResult<fixpoint::Qualifier> {
2412        let (args, body) = self.body_to_fixpoint(&qualifier.body, scx)?;
2413        let name = qualifier.def_id.name().to_string();
2414        Ok(fixpoint::Qualifier { name, args, body })
2415    }
2416}
2417
2418fn mk_implies(assumption: fixpoint::Pred, cstr: fixpoint::Constraint) -> fixpoint::Constraint {
2419    fixpoint::Constraint::ForAll(
2420        fixpoint::Bind {
2421            name: fixpoint::Var::Underscore,
2422            sort: fixpoint::Sort::Int,
2423            pred: assumption,
2424        },
2425        Box::new(cstr),
2426    )
2427}
2428
2429fn parse_kvid(kvid: &str) -> fixpoint::KVid {
2430    if kvid.starts_with("k")
2431        && let Some(kvid) = kvid[1..].parse::<u32>().ok()
2432    {
2433        fixpoint::KVid::from_u32(kvid)
2434    } else {
2435        tracked_span_bug!("unexpected kvar name {kvid}")
2436    }
2437}
2438
2439fn parse_local_var(name: &str) -> Option<fixpoint::Var> {
2440    if let Some(rest) = name.strip_prefix('a')
2441        && let Ok(idx) = rest.parse::<u32>()
2442    {
2443        return Some(fixpoint::Var::Local(fixpoint::LocalVar::from(idx)));
2444    }
2445    None
2446}
2447
2448fn parse_global_var(
2449    name: &str,
2450    fun_decl_map: &HashMap<usize, FluxDefId>,
2451    const_decl_map: &HashMap<usize, DefId>,
2452) -> Option<fixpoint::Var> {
2453    if let Some(rest) = name.strip_prefix('c')
2454        && let Ok(idx) = rest.parse::<u32>()
2455    {
2456        return Some(fixpoint::Var::Const(
2457            fixpoint::GlobalVar::from(idx),
2458            const_decl_map.get(&(idx as usize)).copied(),
2459        ));
2460    }
2461    // try parsing as a named global variable
2462    if let Some(rest) = name.strip_prefix("f$")
2463        && let parts = rest.split('$').collect::<Vec<_>>()
2464        && parts.len() == 2
2465        && let Ok(global_idx) = parts[1].parse::<u32>()
2466        && let Some(def_id) = fun_decl_map.get(&(global_idx as usize)).copied()
2467    {
2468        return Some(fixpoint::Var::Global(fixpoint::GlobalVar::from(global_idx), def_id));
2469    }
2470    None
2471}
2472
2473fn parse_param(name: &str) -> Option<fixpoint::Var> {
2474    if let Some(rest) = name.strip_prefix("reftgen$")
2475        && let parts = rest.split('$').collect::<Vec<_>>()
2476        && parts.len() == 2
2477        && let Ok(index) = parts[1].parse::<u32>()
2478    {
2479        let name = Symbol::intern(parts[0]);
2480        let param = EarlyReftParam { index, name };
2481        return Some(fixpoint::Var::Param(param));
2482    }
2483    None
2484}
2485
2486fn parse_data_proj(name: &str) -> Option<fixpoint::Var> {
2487    if let Some(rest) = name.strip_prefix("fld")
2488        && let parts = rest.split('$').collect::<Vec<_>>()
2489        && parts.len() == 2
2490        && let Ok(adt_id) = parts[0].parse::<u32>()
2491        && let Ok(field) = parts[1].parse::<u32>()
2492    {
2493        let adt_id = fixpoint::AdtId::from(adt_id);
2494        return Some(fixpoint::Var::DataProj { adt_id, field });
2495    }
2496    None
2497}
2498
2499fn parse_data_ctor(name: &str) -> Option<fixpoint::Var> {
2500    if let Some(rest) = name.strip_prefix("mkadt")
2501        && let parts = rest.split('$').collect::<Vec<_>>()
2502        && parts.len() == 2
2503        && let Ok(adt_id) = parts[0].parse::<u32>()
2504        && let Ok(variant_idx) = parts[1].parse::<u32>()
2505    {
2506        let adt_id = fixpoint::AdtId::from(adt_id);
2507        let variant_idx = VariantIdx::from(variant_idx);
2508        return Some(fixpoint::Var::DataCtor(adt_id, variant_idx));
2509    }
2510    None
2511}
2512
2513struct SexpParseCtxt<'a> {
2514    local_var_env: &'a mut LocalVarEnv,
2515    fun_decl_map: &'a HashMap<usize, FluxDefId>,
2516    const_decl_map: &'a HashMap<usize, DefId>,
2517}
2518
2519impl<'a> SexpParseCtxt<'a> {
2520    fn new(
2521        local_var_env: &'a mut LocalVarEnv,
2522        fun_decl_map: &'a HashMap<usize, FluxDefId>,
2523        const_decl_map: &'a HashMap<usize, DefId>,
2524    ) -> Self {
2525        Self { local_var_env, fun_decl_map, const_decl_map }
2526    }
2527}
2528
2529impl FromSexp<FixpointTypes> for SexpParseCtxt<'_> {
2530    fn fresh_var(&mut self) -> fixpoint::Var {
2531        fixpoint::Var::Local(self.local_var_env.fresh_name())
2532    }
2533
2534    fn kvar(&self, name: &str) -> Result<fixpoint::KVid, ParseError> {
2535        bug!("TODO: SexpParse: kvar: {name}")
2536    }
2537
2538    fn string(&self, s: &str) -> Result<fixpoint::SymStr, ParseError> {
2539        Ok(fixpoint::SymStr(Symbol::intern(s)))
2540    }
2541
2542    fn var(&self, name: &str) -> Result<fixpoint::Var, ParseError> {
2543        if let Some(var) = parse_local_var(name) {
2544            return Ok(var);
2545        }
2546        if let Some(var) = parse_global_var(name, self.fun_decl_map, self.const_decl_map) {
2547            return Ok(var);
2548        }
2549        if let Some(var) = parse_param(name) {
2550            return Ok(var);
2551        }
2552        if let Some(var) = parse_data_proj(name) {
2553            return Ok(var);
2554        }
2555        if let Some(var) = parse_data_ctor(name) {
2556            return Ok(var);
2557        }
2558        Err(ParseError::err(format!("Unknown variable: {name}")))
2559    }
2560
2561    fn sort(&self, name: &str) -> Result<fixpoint::DataSort, ParseError> {
2562        if let Some(idx) = name.strip_prefix("Adt")
2563            && let Ok(adt_id) = idx.parse::<u32>()
2564        {
2565            return Ok(fixpoint::DataSort::Adt(fixpoint::AdtId::from(adt_id)));
2566        }
2567        if let Some(idx) = name.strip_prefix("OpaqueAdt")
2568            && let Ok(opaque_id) = idx.parse::<u32>()
2569        {
2570            return Ok(fixpoint::DataSort::User(fixpoint::OpaqueId::from(opaque_id)));
2571        }
2572
2573        Err(ParseError::err(format!("Unknown sort: {name}")))
2574    }
2575}