flux_infer/
fixpoint_encoding.rs

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