flux_infer/
fixpoint_encoding.rs

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