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