Skip to main content

flux_infer/
fixpoint_encoding.rs

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