Skip to main content

flux_refineck/
type_env.rs

1mod place_ty;
2
3use std::{iter, ops::ControlFlow};
4
5use flux_common::{
6    bug,
7    dbg::{SpanTrace, debug_assert_eq3},
8    tracked_span_bug, tracked_span_dbg_assert_eq,
9};
10use flux_infer::{
11    infer::{ConstrReason, InferCtxt, InferCtxtAt, InferCtxtRoot, InferResult},
12    refine_tree::Scope,
13};
14use flux_macros::DebugAsJson;
15use flux_middle::{
16    PlaceExt as _,
17    global_env::GlobalEnv,
18    pretty::{PrettyCx, PrettyNested},
19    queries::QueryResult,
20    rty::{
21        BaseTy, Binder, BoundReftKind, BoundVariableKind, Ctor, Expr, ExprKind, FnOutput, FnSig,
22        GenericArg, HoleKind, INNERMOST, Lambda, List, Loc, Mutability, Path, PolyFnSig, PtrKind,
23        Region, SortCtor, SubsetTy, SubsetTyCtor, Ty, TyKind, VariantIdx,
24        canonicalize::{Hoister, LocalHoister},
25        fold::{FallibleTypeFolder, TypeFoldable, TypeVisitable, TypeVisitor},
26        region_matching::{rty_match_regions, ty_match_regions},
27    },
28};
29use flux_rustc_bridge::{
30    self,
31    mir::{BasicBlock, Body, Local, LocalDecl, LocalDecls, Place, PlaceElem},
32    ty,
33};
34use itertools::{Itertools, izip};
35use rustc_data_structures::unord::UnordMap;
36use rustc_index::{IndexSlice, IndexVec};
37use rustc_middle::{mir::RETURN_PLACE, ty::TyCtxt};
38use rustc_span::{Span, Symbol};
39use rustc_type_ir::BoundVar;
40use serde::Serialize;
41
42use self::place_ty::{LocKind, PlacesTree};
43use super::rty::Sort;
44
45#[derive(Clone, Default)]
46pub struct TypeEnv<'a> {
47    bindings: PlacesTree,
48    local_decls: &'a LocalDecls,
49}
50
51pub struct BasicBlockEnvShape {
52    scope: Scope,
53    bindings: PlacesTree,
54}
55
56pub struct BasicBlockEnv {
57    data: Binder<BasicBlockEnvData>,
58    scope: Scope,
59}
60
61#[derive(Debug)]
62struct BasicBlockEnvData {
63    constrs: List<Expr>,
64    bindings: PlacesTree,
65}
66
67impl<'a> TypeEnv<'a> {
68    pub fn new(infcx: &mut InferCtxt, body: &'a Body, fn_sig: &FnSig) -> TypeEnv<'a> {
69        let mut env = TypeEnv { bindings: PlacesTree::default(), local_decls: &body.local_decls };
70
71        for requires in fn_sig.requires() {
72            infcx.assume_pred(requires);
73        }
74
75        for (local, ty) in body.args_iter().zip(fn_sig.inputs()) {
76            let ty = infcx.unpack(ty);
77            infcx.assume_invariants(&ty);
78            env.alloc_with_ty(local, ty);
79        }
80
81        for local in body.vars_and_temps_iter() {
82            env.alloc(local);
83        }
84
85        env.alloc(RETURN_PLACE);
86        env
87    }
88
89    pub fn empty() -> TypeEnv<'a> {
90        TypeEnv { bindings: PlacesTree::default(), local_decls: IndexSlice::empty() }
91    }
92
93    fn alloc_with_ty(&mut self, local: Local, ty: Ty) {
94        let ty = ty_match_regions(&ty, &self.local_decls[local].ty);
95        self.bindings.insert(local.into(), LocKind::Local, ty);
96    }
97
98    fn alloc(&mut self, local: Local) {
99        self.bindings
100            .insert(local.into(), LocKind::Local, Ty::uninit());
101    }
102
103    pub(crate) fn into_infer(self, scope: Scope) -> BasicBlockEnvShape {
104        BasicBlockEnvShape::new(scope, self)
105    }
106
107    pub(crate) fn lookup_rust_ty(&self, genv: GlobalEnv, place: &Place) -> QueryResult<ty::Ty> {
108        Ok(place.ty(genv, self.local_decls)?.ty)
109    }
110
111    pub(crate) fn lookup_place(
112        &mut self,
113        infcx: &mut InferCtxtAt,
114        place: &Place,
115    ) -> InferResult<Ty> {
116        let span = infcx.span;
117        let result = self.bindings.lookup_unfolding(infcx, place, span)?;
118        Ok(result.ty)
119    }
120
121    pub(crate) fn get(&self, path: &Path) -> Ty {
122        self.bindings.get(path)
123    }
124
125    pub fn update_path(&mut self, path: &Path, new_ty: Ty, span: Span) {
126        self.bindings.lookup(path, span).update(new_ty);
127    }
128
129    /// When checking a borrow in the right hand side of an assignment `x = &'?n p`, we use the
130    /// annotated region `'?n` in the type of the result. This region will only be used temporarily
131    /// and then replaced by the region in the type of `x` after the assignment. See [`TypeEnv::assign`]
132    pub(crate) fn borrow(
133        &mut self,
134        infcx: &mut InferCtxtAt,
135        re: Region,
136        mutbl: Mutability,
137        place: &Place,
138    ) -> InferResult<Ty> {
139        let span = infcx.span;
140        let result = self.bindings.lookup_unfolding(infcx, place, span)?;
141        if result.is_strg && mutbl == Mutability::Mut {
142            Ok(Ty::ptr(PtrKind::Mut(re), result.path()))
143        } else {
144            // FIXME(nilehmann) we should block the place here. That would require a notion
145            // of shared vs mutable block types because sometimes blocked places from a shared
146            // reference never get unblocked and we should still allow reads through them.
147            Ok(Ty::mk_ref(re, result.ty, mutbl))
148        }
149    }
150
151    // FIXME(nilehmann) this is only used in a single place and we have it because [`TypeEnv`]
152    // doesn't expose a lookup without unfolding
153    pub(crate) fn ptr_to_ref_at_place(
154        &mut self,
155        infcx: &mut InferCtxtAt,
156        place: &Place,
157    ) -> InferResult {
158        let lookup = self.bindings.lookup(place, infcx.span);
159        let TyKind::Ptr(PtrKind::Mut(re), path) = lookup.ty.kind() else {
160            tracked_span_bug!("ptr_to_borrow called on non mutable pointer type")
161        };
162
163        let ref_ty =
164            self.ptr_to_ref(infcx, ConstrReason::Other, *re, path, PtrToRefBound::Infer)?;
165
166        self.bindings.lookup(place, infcx.span).update(ref_ty);
167
168        Ok(())
169    }
170
171    /// Convert a (strong) pointer to a mutable reference.
172    ///
173    /// This roughly implements the following inference rule:
174    /// ```text
175    ///                   t₁ <: t₂
176    /// -------------------------------------------------
177    /// Γ₁,ℓ:t1,Γ₂ ; ptr(mut, ℓ) => Γ₁,ℓ:†t₂,Γ₂ ; &mut t2
178    /// ```
179    /// That's it, we first get the current type `t₁` at location `ℓ` and check it is a subtype
180    /// of `t₂`. Then, we update the type of `ℓ` to `t₂` and block the place.
181    ///
182    /// The bound `t₂` can be either inferred ([`PtrToRefBound::Infer`]), explicitly provided
183    /// ([`PtrToRefBound::Ty`]), or made equal to `t₁` ([`PtrToRefBound::Identity`]).
184    ///
185    /// As an example, consider the environment `x: i32[a]` and the pointer `ptr(mut, x)`.
186    /// Converting the pointer to a mutable reference with an inferred bound produces the following
187    /// derivation (roughly):
188    ///
189    /// ```text
190    ///                    i32[a] <: i32{v: $k(v)}
191    /// ----------------------------------------------------------------
192    /// x: i32[a] ; ptr(mut, x) => x:†i32{v: $k(v)} ; &mut i32{v: $k(v)}
193    /// ```
194    pub(crate) fn ptr_to_ref(
195        &mut self,
196        infcx: &mut InferCtxtAt,
197        reason: ConstrReason,
198        re: Region,
199        path: &Path,
200        bound: PtrToRefBound,
201    ) -> InferResult<Ty> {
202        // ℓ: t1
203        let t1 = self.bindings.lookup(path, infcx.span).fold(infcx)?;
204
205        // t1 <: t2
206        let t2 = match bound {
207            PtrToRefBound::Ty(t2) => {
208                let t2 = rty_match_regions(&t2, &t1);
209                infcx.subtyping_with_env(self, &t1, &t2, reason)?;
210                t2
211            }
212            PtrToRefBound::Infer => {
213                let t2 = t1.with_holes().replace_holes(|sorts, kind| {
214                    debug_assert_eq!(kind, HoleKind::Pred);
215                    infcx.fresh_kvar(sorts)
216                });
217                infcx.subtyping_with_env(self, &t1, &t2, reason)?;
218                t2
219            }
220            PtrToRefBound::Identity => t1.clone(),
221        };
222
223        // ℓ: †t2
224        self.bindings
225            .lookup(path, infcx.span)
226            .block_with(t2.clone());
227
228        Ok(Ty::mk_ref(re, t2, Mutability::Mut))
229    }
230
231    pub(crate) fn fold_local_ptrs(&mut self, infcx: &mut InferCtxtAt) -> InferResult {
232        for (loc, bound, ty) in self.bindings.local_ptrs() {
233            infcx.subtyping(&ty, &bound, ConstrReason::FoldLocal)?;
234            self.bindings.remove_local(&loc);
235        }
236        Ok(())
237    }
238
239    /// Updates the type of `place` to `new_ty`. This may involve a *strong update* if we have
240    /// ownership of `place` or a *weak update* if it's behind a reference (which fires a subtyping
241    /// constraint)
242    ///
243    /// When strong updating, the process involves recovering the original regions (lifetimes) used
244    /// in the (unrefined) Rust type of `place` and then substituting these regions in `new_ty`. For
245    /// instance, if we are assigning a value of type `S<&'?10 i32{v: v > 0}>` to a variable `x`,
246    /// and the (unrefined) Rust type of `x` is `S<&'?5 i32>`, before the assignment, we identify a
247    /// substitution that maps the region `'?10` to `'?5`. After applying this substitution, the
248    /// type of the place `x` is updated accordingly. This ensures that the lifetimes in the
249    /// assigned type are consistent with those expected by the place's original type definition.
250    pub(crate) fn assign(
251        &mut self,
252        infcx: &mut InferCtxtAt,
253        place: &Place,
254        new_ty: Ty,
255    ) -> InferResult {
256        let rustc_ty = place.ty(infcx.genv, self.local_decls)?.ty;
257        let new_ty = ty_match_regions(&new_ty, &rustc_ty);
258        let span = infcx.span;
259        let result = self.bindings.lookup_unfolding(infcx, place, span)?;
260        if result.is_strg {
261            result.update(new_ty);
262        } else if !place.behind_raw_ptr(infcx.genv, self.local_decls)? {
263            infcx.subtyping(&new_ty, &result.ty, ConstrReason::Assign)?;
264        }
265        Ok(())
266    }
267
268    pub(crate) fn move_place(&mut self, infcx: &mut InferCtxtAt, place: &Place) -> InferResult<Ty> {
269        let span = infcx.span;
270        let result = self.bindings.lookup_unfolding(infcx, place, span)?;
271        if result.is_strg {
272            let uninit = Ty::uninit();
273            Ok(result.update(uninit))
274        } else {
275            // ignore the 'move' and trust rustc managed the move correctly
276            // https://github.com/flux-rs/flux/issues/725#issuecomment-2295065634
277            Ok(result.ty)
278        }
279    }
280
281    pub(crate) fn unpack(&mut self, infcx: &mut InferCtxt) {
282        self.bindings
283            .fmap_mut(|_loc, ty| infcx.hoister(true).hoist(ty));
284    }
285
286    pub(crate) fn unblock(&mut self, infcx: &mut InferCtxt, place: &Place) {
287        self.bindings.unblock(infcx, place);
288    }
289
290    pub(crate) fn check_goto(
291        self,
292        infcx: &mut InferCtxtAt,
293        bb_env: &BasicBlockEnv,
294        target: BasicBlock,
295    ) -> InferResult {
296        infcx.ensure_resolved_evars(|infcx| {
297            let bb_env = bb_env
298                .data
299                .replace_bound_refts_with(|sort, mode, _| infcx.fresh_infer_var(sort, mode));
300
301            // Check constraints
302            for constr in &bb_env.constrs {
303                infcx.check_pred(constr, ConstrReason::Goto(target));
304            }
305
306            // Check subtyping
307            let bb_env = bb_env.bindings.flatten();
308            for (path, _, ty2) in bb_env {
309                let ty1 = self.bindings.get(&path);
310                infcx.subtyping(&ty1.unblocked(), &ty2.unblocked(), ConstrReason::Goto(target))?;
311            }
312            Ok(())
313        })
314    }
315
316    pub(crate) fn fold(&mut self, infcx: &mut InferCtxtAt, place: &Place) -> InferResult {
317        let span = infcx.span;
318        self.bindings.lookup(place, span).fold(infcx)?;
319        Ok(())
320    }
321
322    pub(crate) fn unfold_local_ptr(
323        &mut self,
324        infcx: &mut InferCtxt,
325        bound: &Ty,
326    ) -> InferResult<Loc> {
327        let name = infcx.define_unknown_var(&Sort::Loc);
328        let loc = Loc::from(name);
329        let ty = infcx.unpack(bound);
330        self.bindings
331            .insert(loc, LocKind::LocalPtr(bound.clone()), ty);
332        Ok(loc)
333    }
334
335    /// ```text
336    /// -----------------------------------
337    /// Γ ; &strg <ℓ: t> => Γ,ℓ: t ; ptr(ℓ)
338    /// ```
339    pub(crate) fn unfold_strg_ref(
340        &mut self,
341        infcx: &mut InferCtxt,
342        path: &Path,
343        ty: &Ty,
344    ) -> InferResult<Loc> {
345        if let Some(loc) = path.to_loc() {
346            let ty = infcx.unpack(ty);
347            self.bindings.insert(loc, LocKind::Universal, ty);
348            Ok(loc)
349        } else {
350            bug!("unfold_strg_ref: unexpected path {path:?}")
351        }
352    }
353
354    pub(crate) fn unfold(
355        &mut self,
356        infcx: &mut InferCtxt,
357        place: &Place,
358        span: Span,
359    ) -> InferResult {
360        self.bindings.unfold(infcx, place, span)
361    }
362
363    pub(crate) fn downcast(
364        &mut self,
365        infcx: &mut InferCtxtAt,
366        place: &Place,
367        variant_idx: VariantIdx,
368    ) -> InferResult {
369        let mut down_place = place.clone();
370        let span = infcx.span;
371        down_place
372            .projection
373            .push(PlaceElem::Downcast(None, variant_idx));
374        self.bindings.unfold(infcx, &down_place, span)?;
375        Ok(())
376    }
377
378    pub fn fully_resolve_evars(&mut self, infcx: &InferCtxt) {
379        self.bindings
380            .fmap_mut(|_loc, ty| infcx.fully_resolve_evars(ty));
381    }
382}
383
384pub(crate) enum PtrToRefBound {
385    Ty(Ty),
386    Infer,
387    Identity,
388}
389
390impl flux_infer::infer::LocEnv for TypeEnv<'_> {
391    fn ptr_to_ref(
392        &mut self,
393        infcx: &mut InferCtxtAt,
394        reason: ConstrReason,
395        re: Region,
396        path: &Path,
397        bound: Ty,
398    ) -> InferResult<Ty> {
399        self.ptr_to_ref(infcx, reason, re, path, PtrToRefBound::Ty(bound))
400    }
401
402    fn get(&self, path: &Path) -> Ty {
403        self.get(path)
404    }
405
406    fn unfold_strg_ref(&mut self, infcx: &mut InferCtxt, path: &Path, ty: &Ty) -> InferResult<Loc> {
407        self.unfold_strg_ref(infcx, path, ty)
408    }
409
410    fn unfold_local_ptr(&mut self, infcx: &mut InferCtxt, bound: &Ty) -> InferResult<Loc> {
411        self.unfold_local_ptr(infcx, bound)
412    }
413
414    fn fold_local_ptrs(&mut self, infcx: &mut InferCtxtAt) -> InferResult {
415        self.fold_local_ptrs(infcx)
416    }
417
418    fn update_path(&mut self, path: &Path, new_ty: Ty, span: Span) {
419        self.update_path(path, new_ty, span);
420    }
421}
422
423impl BasicBlockEnvShape {
424    pub fn enter<'a>(&self, local_decls: &'a LocalDecls) -> TypeEnv<'a> {
425        TypeEnv { bindings: self.bindings.clone(), local_decls }
426    }
427
428    fn new(scope: Scope, env: TypeEnv) -> BasicBlockEnvShape {
429        let mut bindings = env.bindings;
430        bindings.fmap_mut(|_loc, ty| BasicBlockEnvShape::pack_ty(&scope, ty));
431        BasicBlockEnvShape { scope, bindings }
432    }
433
434    fn pack_ty(scope: &Scope, ty: &Ty) -> Ty {
435        match ty.kind() {
436            TyKind::Indexed(bty, idxs) => {
437                let bty = BasicBlockEnvShape::pack_bty(scope, bty);
438                if scope.has_free_vars(idxs) {
439                    Ty::exists_with_constr(bty, Expr::hole(HoleKind::Pred))
440                } else {
441                    Ty::indexed(bty, idxs.clone())
442                }
443            }
444            TyKind::Downcast(adt, args, ty, variant, fields) => {
445                debug_assert!(!scope.has_free_vars(args));
446                debug_assert!(!scope.has_free_vars(ty));
447                let fields = fields.iter().map(|ty| Self::pack_ty(scope, ty)).collect();
448                Ty::downcast(adt.clone(), args.clone(), ty.clone(), *variant, fields)
449            }
450            TyKind::Blocked(ty) => Ty::blocked(BasicBlockEnvShape::pack_ty(scope, ty)),
451            // FIXME(nilehmann) [`TyKind::Exists`] could also contain free variables.
452            TyKind::Exists(_)
453            | TyKind::Discr(..)
454            | TyKind::Ptr(..)
455            | TyKind::Uninit
456            | TyKind::Param(_)
457            | TyKind::Constr(_, _) => ty.clone(),
458            TyKind::Infer(_) => bug!("unexpected hole whecn checking function body"),
459            TyKind::StrgRef(..) => bug!("unexpected strong reference when checking function body"),
460        }
461    }
462
463    fn pack_bty(scope: &Scope, bty: &BaseTy) -> BaseTy {
464        match bty {
465            BaseTy::Adt(adt_def, args) => {
466                let args = List::from_vec(
467                    args.iter()
468                        .map(|arg| Self::pack_generic_arg(scope, arg))
469                        .collect(),
470                );
471                BaseTy::adt(adt_def.clone(), args)
472            }
473            BaseTy::FnDef(def_id, args) => {
474                let args = List::from_vec(
475                    args.iter()
476                        .map(|arg| Self::pack_generic_arg(scope, arg))
477                        .collect(),
478                );
479                BaseTy::fn_def(*def_id, args)
480            }
481            BaseTy::Tuple(tys) => {
482                let tys = tys
483                    .iter()
484                    .map(|ty| BasicBlockEnvShape::pack_ty(scope, ty))
485                    .collect();
486                BaseTy::Tuple(tys)
487            }
488            BaseTy::Slice(ty) => BaseTy::Slice(Self::pack_ty(scope, ty)),
489            BaseTy::Ref(r, ty, mutbl) => BaseTy::Ref(*r, Self::pack_ty(scope, ty), *mutbl),
490            BaseTy::Array(ty, c) => BaseTy::Array(Self::pack_ty(scope, ty), c.clone()),
491            BaseTy::Int(_)
492            | BaseTy::Param(_)
493            | BaseTy::Uint(_)
494            | BaseTy::Bool
495            | BaseTy::Float(_)
496            | BaseTy::Str
497            | BaseTy::RawPtr(_, _)
498            | BaseTy::RawPtrMetadata(_)
499            | BaseTy::Char
500            | BaseTy::Never
501            | BaseTy::Closure(..)
502            | BaseTy::Dynamic(..)
503            | BaseTy::Alias(..)
504            | BaseTy::FnPtr(..)
505            | BaseTy::Foreign(..)
506            | BaseTy::Coroutine(..) => {
507                if scope.has_free_vars(bty) {
508                    tracked_span_bug!("unexpected type with free vars")
509                } else {
510                    bty.clone()
511                }
512            }
513            BaseTy::Pat => {
514                todo!()
515            }
516            BaseTy::Infer(..) => {
517                tracked_span_bug!("unexpected infer type")
518            }
519        }
520    }
521
522    fn pack_generic_arg(scope: &Scope, arg: &GenericArg) -> GenericArg {
523        match arg {
524            GenericArg::Ty(ty) => GenericArg::Ty(Self::pack_ty(scope, ty)),
525            GenericArg::Base(ctor) => GenericArg::Base(Self::pack_subset_ty_ctor(scope, ctor)),
526            GenericArg::Lifetime(re) => GenericArg::Lifetime(*re),
527            GenericArg::Const(c) => GenericArg::Const(c.clone()),
528        }
529    }
530
531    fn pack_subset_ty_ctor(scope: &Scope, ctor: &SubsetTyCtor) -> SubsetTyCtor {
532        let sty = ctor.as_ref().skip_binder();
533        debug_assert!(sty.idx.is_nu());
534        let bty = Self::pack_bty(scope, &sty.bty);
535        let pred = if scope.has_free_vars(&sty.pred) {
536            Expr::hole(HoleKind::Pred)
537        } else {
538            sty.pred.clone()
539        };
540        let sort = bty.sort();
541        Binder::bind_with_sort(SubsetTy::new(bty, Expr::nu(), pred), sort)
542    }
543
544    fn update(&mut self, path: &Path, ty: Ty, span: Span) {
545        self.bindings.lookup(path, span).update(ty);
546    }
547
548    /// join(self, genv, other) consumes the bindings in other, to "update"
549    /// `self` in place, and returns `true` if there was an actual change
550    /// or `false` indicating no change (i.e., a fixpoint was reached).
551    pub(crate) fn join(&mut self, other: TypeEnv, span: Span) -> bool {
552        let paths = self.bindings.paths();
553
554        // Join types
555        let mut modified = false;
556        for path in &paths {
557            let ty1 = self.bindings.get(path);
558            let ty2 = other.bindings.get(path);
559            let ty = if ty1 == ty2 { ty1.clone() } else { self.join_ty(&ty1, &ty2) };
560            modified |= ty1 != ty;
561            self.update(path, ty, span);
562        }
563
564        modified
565    }
566
567    fn join_ty(&self, ty1: &Ty, ty2: &Ty) -> Ty {
568        match (ty1.kind(), ty2.kind()) {
569            (TyKind::Blocked(ty1), _) => Ty::blocked(self.join_ty(ty1, &ty2.unblocked())),
570            (_, TyKind::Blocked(ty2)) => Ty::blocked(self.join_ty(&ty1.unblocked(), ty2)),
571            (TyKind::Uninit, _) | (_, TyKind::Uninit) => Ty::uninit(),
572            (TyKind::Exists(ty1), _) => self.join_ty(ty1.as_ref().skip_binder(), ty2),
573            (_, TyKind::Exists(ty2)) => self.join_ty(ty1, ty2.as_ref().skip_binder()),
574            (TyKind::Constr(_, ty1), _) => self.join_ty(ty1, ty2),
575            (_, TyKind::Constr(_, ty2)) => self.join_ty(ty1, ty2),
576            (TyKind::Indexed(bty1, idx1), TyKind::Indexed(bty2, idx2)) => {
577                let bty = self.join_bty(bty1, bty2);
578                let mut sorts = vec![];
579                let idx = self.join_idx(idx1, idx2, &bty.sort(), &mut sorts);
580                if sorts.is_empty() {
581                    Ty::indexed(bty, idx)
582                } else {
583                    let ty = Ty::constr(Expr::hole(HoleKind::Pred), Ty::indexed(bty, idx));
584                    Ty::exists(Binder::bind_with_sorts(ty, &sorts))
585                }
586            }
587            (TyKind::Ptr(rk1, path1), TyKind::Ptr(rk2, path2)) => {
588                debug_assert_eq!(rk1, rk2);
589                debug_assert_eq!(path1, path2);
590                Ty::ptr(*rk1, path1.clone())
591            }
592            (TyKind::Param(param_ty1), TyKind::Param(param_ty2)) => {
593                debug_assert_eq!(param_ty1, param_ty2);
594                Ty::param(*param_ty1)
595            }
596            (
597                TyKind::Downcast(adt1, args1, ty1, variant1, fields1),
598                TyKind::Downcast(adt2, args2, ty2, variant2, fields2),
599            ) => {
600                debug_assert_eq!(adt1, adt2);
601                debug_assert_eq!(args1, args2);
602                debug_assert!(ty1 == ty2 && !self.scope.has_free_vars(ty2));
603                debug_assert_eq!(variant1, variant2);
604                debug_assert_eq!(fields1.len(), fields2.len());
605                let fields = iter::zip(fields1, fields2)
606                    .map(|(ty1, ty2)| self.join_ty(ty1, ty2))
607                    .collect();
608                Ty::downcast(adt1.clone(), args1.clone(), ty1.clone(), *variant1, fields)
609            }
610            _ => tracked_span_bug!("unexpected types: `{ty1:?}` - `{ty2:?}`"),
611        }
612    }
613
614    fn join_idx(&self, e1: &Expr, e2: &Expr, sort: &Sort, bound_sorts: &mut Vec<Sort>) -> Expr {
615        match (e1.kind(), e2.kind(), sort) {
616            (ExprKind::Tuple(es1), ExprKind::Tuple(es2), Sort::Tuple(sorts)) => {
617                debug_assert_eq3!(es1.len(), es2.len(), sorts.len());
618                Expr::tuple(
619                    izip!(es1, es2, sorts)
620                        .map(|(e1, e2, sort)| self.join_idx(e1, e2, sort, bound_sorts))
621                        .collect(),
622                )
623            }
624            (
625                ExprKind::Ctor(Ctor::Struct(_), flds1),
626                ExprKind::Ctor(Ctor::Struct(_), flds2),
627                Sort::App(SortCtor::Adt(sort_def), args),
628            ) => {
629                let sorts = sort_def.struct_variant().field_sorts(args);
630                debug_assert_eq3!(flds1.len(), flds2.len(), sorts.len());
631
632                Expr::ctor_struct(
633                    sort_def.did(),
634                    izip!(flds1, flds2, &sorts)
635                        .map(|(f1, f2, sort)| self.join_idx(f1, f2, sort, bound_sorts))
636                        .collect(),
637                )
638            }
639            _ => {
640                let has_free_vars2 = self.scope.has_free_vars(e2);
641                let has_escaping_vars1 = e1.has_escaping_bvars();
642                let has_escaping_vars2 = e2.has_escaping_bvars();
643                if !has_free_vars2 && !has_escaping_vars1 && !has_escaping_vars2 && e1 == e2 {
644                    e1.clone()
645                } else if sort.is_pred() {
646                    // FIXME(nilehmann) we shouldn't special case predicates here. Instead, we
647                    // should differentiate between generics and indices.
648                    let fsort = sort.expect_func().expect_mono();
649                    Expr::abs(Lambda::bind_with_fsort(Expr::hole(HoleKind::Pred), fsort))
650                } else {
651                    bound_sorts.push(sort.clone());
652                    Expr::bvar(
653                        INNERMOST,
654                        BoundVar::from_usize(bound_sorts.len() - 1),
655                        BoundReftKind::Anon,
656                    )
657                }
658            }
659        }
660    }
661
662    fn join_bty(&self, bty1: &BaseTy, bty2: &BaseTy) -> BaseTy {
663        match (bty1, bty2) {
664            (BaseTy::Adt(def1, args1), BaseTy::Adt(def2, args2)) => {
665                tracked_span_dbg_assert_eq!(def1.did(), def2.did());
666                let args = iter::zip(args1, args2)
667                    .map(|(arg1, arg2)| self.join_generic_arg(arg1, arg2))
668                    .collect();
669                BaseTy::adt(def1.clone(), List::from_vec(args))
670            }
671            (BaseTy::Tuple(fields1), BaseTy::Tuple(fields2)) => {
672                let fields = iter::zip(fields1, fields2)
673                    .map(|(ty1, ty2)| self.join_ty(ty1, ty2))
674                    .collect();
675                BaseTy::Tuple(fields)
676            }
677            (BaseTy::Alias(alias_ty1), BaseTy::Alias(alias_ty2)) => {
678                tracked_span_dbg_assert_eq!(alias_ty1, alias_ty2);
679                BaseTy::Alias(alias_ty1.clone())
680            }
681            (BaseTy::Ref(r1, ty1, mutbl1), BaseTy::Ref(r2, ty2, mutbl2)) => {
682                tracked_span_dbg_assert_eq!(r1, r2);
683                tracked_span_dbg_assert_eq!(mutbl1, mutbl2);
684                BaseTy::Ref(*r1, self.join_ty(ty1, ty2), *mutbl1)
685            }
686            (BaseTy::Array(ty1, len1), BaseTy::Array(ty2, len2)) => {
687                tracked_span_dbg_assert_eq!(len1, len2);
688                BaseTy::Array(self.join_ty(ty1, ty2), len1.clone())
689            }
690            (BaseTy::Slice(ty1), BaseTy::Slice(ty2)) => BaseTy::Slice(self.join_ty(ty1, ty2)),
691            (BaseTy::FnPtr(sig1), BaseTy::FnPtr(sig2)) if sig1 != sig2 => {
692                // Generalize to a signature with holes, which are replaced by kvars in the basic
693                // block env, e.g., `fn(i32{v: $k0(v)}) -> i32{v: $k1(v)}`. Fn subtyping (against
694                // both signatures) is checked when jumping to the join point.
695                BaseTy::FnPtr(generalize_fn_sig(sig1))
696            }
697            _ => {
698                tracked_span_dbg_assert_eq!(bty1, bty2);
699                bty1.clone()
700            }
701        }
702    }
703
704    fn join_generic_arg(&self, arg1: &GenericArg, arg2: &GenericArg) -> GenericArg {
705        match (arg1, arg2) {
706            (GenericArg::Ty(ty1), GenericArg::Ty(ty2)) => GenericArg::Ty(self.join_ty(ty1, ty2)),
707            (GenericArg::Base(ctor1), GenericArg::Base(ctor2)) => {
708                let sty1 = ctor1.as_ref().skip_binder();
709                let sty2 = ctor2.as_ref().skip_binder();
710                debug_assert!(sty1.idx.is_nu());
711                debug_assert!(sty2.idx.is_nu());
712
713                let bty = self.join_bty(&sty1.bty, &sty2.bty);
714                let pred = if self.scope.has_free_vars(&sty2.pred) || sty1.pred != sty2.pred {
715                    Expr::hole(HoleKind::Pred)
716                } else {
717                    sty1.pred.clone()
718                };
719                let sort = bty.sort();
720                let ctor = Binder::bind_with_sort(SubsetTy::new(bty, Expr::nu(), pred), sort);
721                GenericArg::Base(ctor)
722            }
723            (GenericArg::Lifetime(re1), GenericArg::Lifetime(_re2)) => {
724                // TODO(nilehmann) loop_abstract_refinement.rs is triggering this assertion to fail
725                // wee should fix it.
726                // debug_assert_eq!(re1, _re2);
727                GenericArg::Lifetime(*re1)
728            }
729            (GenericArg::Const(c1), GenericArg::Const(c2)) => {
730                debug_assert_eq!(c1, c2);
731                GenericArg::Const(c1.clone())
732            }
733            _ => tracked_span_bug!("unexpected generic args: `{arg1:?}` - `{arg2:?}`"),
734        }
735    }
736
737    pub fn into_bb_env(self, infcx: &mut InferCtxtRoot, body: &Body) -> BasicBlockEnv {
738        let mut delegate = LocalHoister::default();
739        let mut hoister = Hoister::with_delegate(&mut delegate).transparent();
740
741        let mut bindings = self.bindings;
742        bindings.fmap_mut(|loc, ty| {
743            let name = if let Loc::Local(local) = loc {
744                body.local_names.get(local).copied()
745            } else {
746                None
747            };
748            hoister.delegate.name = name;
749            hoister.hoist(ty)
750        });
751
752        BasicBlockEnv {
753            // We are relying on all the types in `bindings` not having escaping bvars, otherwise
754            // we would have to shift them in since we are creating a new binder.
755            data: delegate.bind(|vars, preds| {
756                // Replace all holes with a single fresh kvar on all parameters
757                let mut constrs = preds
758                    .into_iter()
759                    .filter(|pred| !matches!(pred.kind(), ExprKind::Hole(HoleKind::Pred)))
760                    .collect_vec();
761                let kvar = infcx.fresh_kvar_in_scope(std::slice::from_ref(&vars), &self.scope);
762                constrs.push(kvar);
763
764                // Replace remaining holes by fresh kvars
765                let mut kvar_gen = |binders: &[_], kind| {
766                    debug_assert_eq!(kind, HoleKind::Pred);
767                    let binders = std::iter::once(vars.clone())
768                        .chain(binders.iter().cloned())
769                        .collect_vec();
770                    infcx.fresh_kvar_in_scope(&binders, &self.scope)
771                };
772                bindings.fmap_mut(|_, binding| binding.replace_holes(&mut kvar_gen));
773
774                BasicBlockEnvData { constrs: constrs.into(), bindings }
775            }),
776            scope: self.scope,
777        }
778    }
779}
780
781/// Generalizes a fn pointer signature to one with the same (rust) shape where all refinements are
782/// holes, e.g., `for<n> fn(i32[n]) -> i32[n + 1]` becomes `fn({v. i32[v] | *}) -> {v. i32[v] | *}`.
783/// The refinement params of the signature are dropped, and so are the `requires`, `ensures` and
784/// `no_panic` (which could mention them). The region vars are kept: they come before the
785/// refinement params in the binder, so dropping the latter doesn't shift them.
786///
787/// This is the same template that refining the rust signature with [`Refiner::with_holes`] would
788/// produce (see the `Refine` impl for `ty::FnSig`), but it doesn't need a refiner.
789///
790/// [`Refiner::with_holes`]: flux_middle::rty::refining::Refiner::with_holes
791fn generalize_fn_sig(sig: &PolyFnSig) -> PolyFnSig {
792    let vars = sig
793        .vars()
794        .iter()
795        .filter(|var| matches!(var, BoundVariableKind::Region(_)))
796        .cloned()
797        .collect();
798    let fn_sig = sig.skip_binder_ref();
799    let ret = fn_sig.output().skip_binder().ret.with_holes();
800    let fn_sig = FnSig::new(
801        fn_sig.safety,
802        fn_sig.abi,
803        List::empty(),
804        fn_sig.inputs.with_holes(),
805        Binder::dummy(FnOutput::new(ret, vec![])),
806        Expr::ff(),
807        fn_sig.lifted,
808    );
809    Binder::bind_with_vars(fn_sig, vars)
810}
811
812impl TypeVisitable for BasicBlockEnvData {
813    fn visit_with<V: TypeVisitor>(&self, _visitor: &mut V) -> ControlFlow<V::BreakTy> {
814        unimplemented!()
815    }
816}
817
818impl TypeFoldable for BasicBlockEnvData {
819    fn try_fold_with<F: FallibleTypeFolder>(
820        &self,
821        folder: &mut F,
822    ) -> std::result::Result<Self, F::Error> {
823        Ok(BasicBlockEnvData {
824            constrs: self.constrs.try_fold_with(folder)?,
825            bindings: self.bindings.try_fold_with(folder)?,
826        })
827    }
828}
829
830impl BasicBlockEnv {
831    pub(crate) fn enter<'a>(
832        &self,
833        infcx: &mut InferCtxt,
834        local_decls: &'a LocalDecls,
835    ) -> TypeEnv<'a> {
836        let data = self.data.replace_bound_refts_with(|sort, _, kind| {
837            Expr::fvar(infcx.define_bound_reft_var(sort, kind))
838        });
839        for constr in &data.constrs {
840            infcx.assume_pred(constr);
841        }
842        TypeEnv { bindings: data.bindings, local_decls }
843    }
844
845    pub(crate) fn scope(&self) -> &Scope {
846        &self.scope
847    }
848}
849
850mod pretty {
851    use std::fmt;
852
853    use flux_middle::pretty::*;
854
855    use super::*;
856
857    impl Pretty for TypeEnv<'_> {
858        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
859            w!(cx, f, "{:?}", &self.bindings)
860        }
861
862        fn default_cx(tcx: TyCtxt) -> PrettyCx {
863            PlacesTree::default_cx(tcx)
864        }
865    }
866
867    impl Pretty for BasicBlockEnvShape {
868        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
869            w!(cx, f, "{:?} {:?}", &self.scope, &self.bindings)
870        }
871
872        fn default_cx(tcx: TyCtxt) -> PrettyCx {
873            PlacesTree::default_cx(tcx)
874        }
875    }
876
877    impl Pretty for BasicBlockEnv {
878        fn fmt(&self, cx: &PrettyCx, f: &mut fmt::Formatter<'_>) -> fmt::Result {
879            w!(cx, f, "{:?} ", &self.scope)?;
880
881            let vars = self.data.vars();
882            cx.with_bound_vars(vars, || {
883                if !vars.is_empty() {
884                    cx.fmt_bound_vars(true, "for<", vars, "> ", f)?;
885                }
886                let data = self.data.as_ref().skip_binder();
887                if !data.constrs.is_empty() {
888                    w!(
889                        cx,
890                        f,
891                        "{:?} ⇒ ",
892                        join!(", ", data.constrs.iter().filter(|pred| !pred.is_trivially_true()))
893                    )?;
894                }
895                w!(cx, f, "{:?}", &data.bindings)
896            })
897        }
898
899        fn default_cx(tcx: TyCtxt) -> PrettyCx {
900            PlacesTree::default_cx(tcx)
901        }
902    }
903
904    impl_debug_with_default_cx! {
905        TypeEnv<'_> => "type_env",
906        BasicBlockEnvShape => "basic_block_env_shape",
907        BasicBlockEnv => "basic_block_env"
908    }
909}
910
911/// A very explicit representation of [`TypeEnv`] for debugging/tracing/serialization ONLY.
912#[derive(Serialize, DebugAsJson)]
913pub struct TypeEnvTrace(Vec<TypeEnvBind>);
914
915#[derive(Serialize)]
916struct TypeEnvBind {
917    local: LocInfo,
918    name: Option<String>,
919    kind: String,
920    ty: String,
921    span: Option<SpanTrace>,
922}
923
924#[derive(Serialize)]
925enum LocInfo {
926    Local(String),
927    Var(String),
928}
929
930fn loc_info(loc: &Loc) -> LocInfo {
931    match loc {
932        Loc::Local(local) => LocInfo::Local(format!("{local:?}")),
933        Loc::Var(var) => LocInfo::Var(format!("{var:?}")),
934    }
935}
936
937fn loc_name(local_names: &UnordMap<Local, Symbol>, loc: &Loc) -> Option<String> {
938    if let Loc::Local(local) = loc {
939        let name = local_names.get(local)?;
940        return Some(format!("{name}"));
941    }
942    None
943}
944
945fn loc_span(
946    genv: GlobalEnv,
947    local_decls: &IndexVec<Local, LocalDecl>,
948    loc: &Loc,
949) -> Option<SpanTrace> {
950    if let Loc::Local(local) = loc {
951        return local_decls
952            .get(*local)
953            .map(|local_decl| SpanTrace::new(genv.tcx(), local_decl.source_info.span));
954    }
955    None
956}
957
958impl TypeEnvTrace {
959    pub fn new(
960        genv: GlobalEnv,
961        local_names: &UnordMap<Local, Symbol>,
962        local_decls: &IndexVec<Local, LocalDecl>,
963        cx: PrettyCx,
964        env: &TypeEnv,
965    ) -> Self {
966        let mut bindings = vec![];
967        env.bindings
968            .iter()
969            .filter(|(_, binding)| !binding.ty.is_uninit())
970            .sorted_by(|(loc1, _), (loc2, _)| loc1.cmp(loc2))
971            .for_each(|(loc, binding)| {
972                let name = loc_name(local_names, loc);
973                let local = loc_info(loc);
974                let kind = format!("{:?}", binding.kind);
975                let ty = binding.ty.nested_string(&cx);
976                let span = loc_span(genv, local_decls, loc);
977                bindings.push(TypeEnvBind { name, local, kind, ty, span });
978            });
979
980        TypeEnvTrace(bindings)
981    }
982}