flux_infer/
fixpoint_encoding.rs

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