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