flux_infer/
fixpoint_encoding.rs

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