Skip to main content

flux_refineck/
checker.rs

1use std::{collections::hash_map::Entry, iter, vec};
2
3use flux_common::{
4    bug, dbg, dbg::SpanTrace, index::IndexVec, iter::IterExt, span_bug, tracked_span_bug,
5    tracked_span_dbg_assert_eq,
6};
7use flux_config::{self as config, InferOpts};
8use flux_infer::{
9    infer::{
10        ConstrReason, GlobalEnvExt as _, InferCtxt, InferCtxtRoot, InferResult, SubtypeReason,
11    },
12    projections::NormalizeExt as _,
13    refine_tree::{Marker, RefineCtxtTrace},
14};
15use flux_middle::{
16    PanicReason, PanicSpec,
17    global_env::GlobalEnv,
18    pretty::PrettyCx,
19    queries::{QueryResult, try_query},
20    query_bug,
21    rty::{
22        self, AdtDef, AliasReft, BaseTy, BinOp, Binder, Bool, Clause, Constant,
23        CoroutineObligPredicate, EarlyBinder, Expr, FnOutput, FnSig, FnTraitPredicate, GenericArg,
24        GenericArgsExt as _, Int, IntTy, Mutability, Path, PolyFnSig, PtrKind, RefineArgs,
25        RefineArgsExt,
26        Region::ReErased,
27        Sort, SubsetTyCtor, Ty, TyKind, Uint, UintTy, VariantIdx,
28        fold::{TypeFoldable, TypeFolder, TypeSuperFoldable},
29        refining::{Refine, Refiner},
30    },
31};
32use flux_rustc_bridge::{
33    self, ToRustc,
34    mir::{
35        self, AggregateKind, AssertKind, BasicBlock, Body, BodyRoot, BorrowKind, CastKind,
36        ConstOperand, Location, NonDivergingIntrinsic, Operand, Place, Rvalue, START_BLOCK,
37        Statement, StatementKind, Terminator, TerminatorKind, UnOp,
38    },
39    ty::{self, GenericArgsExt as _},
40};
41use itertools::{Itertools, izip};
42use rustc_data_structures::{
43    graph::dominators::Dominators,
44    unord::{UnordMap, UnordSet},
45};
46use rustc_hash::FxHashMap;
47use rustc_hir::{
48    LangItem,
49    def_id::{DefId, LocalDefId},
50};
51use rustc_index::{IndexSlice, bit_set::DenseBitSet};
52use rustc_infer::infer::TyCtxtInferExt;
53use rustc_middle::{
54    mir::{Promoted, SwitchTargets},
55    ty::{TyCtxt, TypeSuperVisitable as _, TypeVisitable as _, TypingMode},
56};
57use rustc_span::{
58    DUMMY_SP, Span, Symbol,
59    sym::{self},
60};
61
62use self::errors::{CheckerError, ResultExt};
63use crate::{
64    checker::mir::RawPtrKind,
65    ghost_statements::{CheckerId, GhostStatement, GhostStatements, Point},
66    primops,
67    queue::WorkQueue,
68    rty::Char,
69    type_env::{BasicBlockEnv, BasicBlockEnvShape, PtrToRefBound, TypeEnv, TypeEnvTrace},
70};
71
72type Result<T = ()> = std::result::Result<T, CheckerError>;
73
74pub(crate) struct Checker<'ck, 'genv, 'tcx, M> {
75    genv: GlobalEnv<'genv, 'tcx>,
76    /// [`CheckerId`] of the function-like item being checked.
77    checker_id: CheckerId,
78    inherited: Inherited<'ck, M>,
79    body: &'ck Body<'tcx>,
80    /// The type used for the `resume` argument if we are checking a generator.
81    resume_ty: Option<Ty>,
82    fn_sig: FnSig,
83    /// A marker to the node in the refinement tree at the end of the basic block after applying
84    /// the effects of the terminator.
85    markers: IndexVec<BasicBlock, Option<Marker>>,
86    visited: DenseBitSet<BasicBlock>,
87    queue: WorkQueue<'ck>,
88    default_refiner: Refiner<'genv, 'tcx>,
89    /// The templates for the promoted bodies of the current function
90    promoted: &'ck IndexSlice<Promoted, Ty>,
91}
92
93/// Fields shared by the top-level function and its nested closure/generators
94struct Inherited<'ck, M> {
95    /// [`Expr`]s used to instantiate the early bound refinement parameters of the top-level function
96    /// signature
97    ghost_stmts: &'ck UnordMap<CheckerId, GhostStatements>,
98    mode: &'ck mut M,
99
100    /// This map has the "templates" generated for the closures constructed (in [`Checker::check_rvalue_closure`]).
101    /// The [`PolyFnSig`] can have free variables (inside the scope of kvars), so we need to be
102    /// careful and only use it in the correct scope.
103    closures: &'ck mut UnordMap<DefId, PolyFnSig>,
104}
105
106#[derive(Debug)]
107struct ResolvedCall {
108    output: Ty,
109    /// The refine arguments given to the call
110    _early_args: Vec<Expr>,
111    /// The refine arguments given to the call
112    _late_args: Vec<Expr>,
113}
114
115impl<'ck, M: Mode> Inherited<'ck, M> {
116    fn new(
117        mode: &'ck mut M,
118        ghost_stmts: &'ck UnordMap<CheckerId, GhostStatements>,
119        closures: &'ck mut UnordMap<DefId, PolyFnSig>,
120    ) -> Self {
121        Self { ghost_stmts, mode, closures }
122    }
123
124    fn reborrow(&mut self) -> Inherited<'_, M> {
125        Inherited { ghost_stmts: self.ghost_stmts, mode: self.mode, closures: self.closures }
126    }
127}
128
129pub(crate) trait Mode: Sized {
130    #[expect(dead_code)]
131    const NAME: &str;
132
133    fn enter_basic_block<'ck, 'genv, 'tcx>(
134        ck: &mut Checker<'ck, 'genv, 'tcx, Self>,
135        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
136        bb: BasicBlock,
137    ) -> TypeEnv<'ck>;
138
139    fn check_goto_join_point<'genv, 'tcx>(
140        ck: &mut Checker<'_, 'genv, 'tcx, Self>,
141        infcx: InferCtxt<'_, 'genv, 'tcx>,
142        env: TypeEnv,
143        terminator_span: Span,
144        target: BasicBlock,
145    ) -> Result<bool>;
146
147    fn clear(ck: &mut Checker<Self>, bb: BasicBlock);
148}
149
150pub(crate) struct ShapeMode {
151    bb_envs: FxHashMap<CheckerId, FxHashMap<BasicBlock, BasicBlockEnvShape>>,
152}
153
154pub(crate) struct RefineMode {
155    bb_envs: FxHashMap<CheckerId, FxHashMap<BasicBlock, BasicBlockEnv>>,
156}
157
158/// The result of running the shape phase.
159pub(crate) struct ShapeResult(FxHashMap<CheckerId, FxHashMap<BasicBlock, BasicBlockEnvShape>>);
160
161/// A `Guard` describes extra "control" information that holds at the start of a successor basic block
162#[derive(Debug)]
163enum Guard {
164    /// No extra information holds, e.g., for a plain goto.
165    None,
166    /// A predicate that can be assumed, e.g., in the branches of an if-then-else.
167    Pred(Expr),
168    /// The corresponding place was found to be of a particular variant.
169    Match(Place, VariantIdx),
170}
171
172impl<'genv, 'tcx> Checker<'_, 'genv, 'tcx, ShapeMode> {
173    pub(crate) fn run_in_shape_mode<'ck>(
174        genv: GlobalEnv<'genv, 'tcx>,
175        local_id: LocalDefId,
176        ghost_stmts: &'ck UnordMap<CheckerId, GhostStatements>,
177        closures: &'ck mut UnordMap<DefId, PolyFnSig>,
178        opts: InferOpts,
179        poly_sig: &PolyFnSig,
180    ) -> Result<ShapeResult> {
181        let def_id = local_id.to_def_id();
182        dbg::shape_mode_span!(genv.tcx(), local_id).in_scope(|| {
183            let span = genv.tcx().def_span(local_id);
184            let mut mode = ShapeMode { bb_envs: FxHashMap::default() };
185
186            let body = genv.mir(local_id).with_span(span)?;
187
188            // In shape mode we don't care about kvars
189            let mut root_ctxt = try_query(|| {
190                genv.infcx_root(&body.infcx, opts)
191                    .with_dummy_kvars()
192                    .identity_for_item(def_id)?
193                    .build()
194            })
195            .with_span(span)?;
196
197            let inherited = Inherited::new(&mut mode, ghost_stmts, closures);
198
199            let infcx = root_ctxt.infcx(def_id, &body.infcx);
200            Checker::run(infcx, local_id, inherited, poly_sig.clone())?;
201
202            Ok(ShapeResult(mode.bb_envs))
203        })
204    }
205}
206
207impl<'genv, 'tcx> Checker<'_, 'genv, 'tcx, RefineMode> {
208    pub(crate) fn run_in_refine_mode<'ck>(
209        genv: GlobalEnv<'genv, 'tcx>,
210        local_id: LocalDefId,
211        ghost_stmts: &'ck UnordMap<CheckerId, GhostStatements>,
212        closures: &'ck mut UnordMap<DefId, PolyFnSig>,
213        bb_env_shapes: ShapeResult,
214        opts: InferOpts,
215        poly_sig: &PolyFnSig,
216    ) -> Result<InferCtxtRoot<'genv, 'tcx>> {
217        let def_id = local_id.to_def_id();
218        let span = genv.tcx().def_span(def_id);
219
220        let body = genv.mir(local_id).with_span(span)?;
221        let mut root_ctxt = try_query(|| {
222            genv.infcx_root(&body.infcx, opts)
223                .identity_for_item(def_id)?
224                .build()
225        })
226        .with_span(span)?;
227        let bb_envs = bb_env_shapes.into_bb_envs(&mut root_ctxt, &body.body);
228
229        dbg::refine_mode_span!(genv.tcx(), def_id, bb_envs).in_scope(|| {
230            // Check the body of the function def_id against its signature
231            let mut mode = RefineMode { bb_envs };
232            let inherited = Inherited::new(&mut mode, ghost_stmts, closures);
233            let infcx = root_ctxt.infcx(def_id, &body.infcx);
234            Checker::run(infcx, local_id, inherited, poly_sig.clone())?;
235
236            Ok(root_ctxt)
237        })
238    }
239}
240
241/// `SubFn` lets us reuse _most_ of the same code for `check_fn_subtyping` for both the case where
242/// we have an early-bound function signature (e.g., for a trait method???) and versions without,
243/// e.g. a plain closure against its FnTraitPredicate obligation.
244#[derive(Debug)]
245pub enum SubFn {
246    Poly(DefId, EarlyBinder<rty::PolyFnSig>, rty::GenericArgs),
247    Mono(rty::PolyFnSig),
248}
249
250impl SubFn {
251    pub fn as_ref(&self) -> &rty::PolyFnSig {
252        match self {
253            SubFn::Poly(_, sig, _) => sig.skip_binder_ref(),
254            SubFn::Mono(sig) => sig,
255        }
256    }
257}
258
259/// The function `check_fn_subtyping` does a function subtyping check between
260/// the sub-type (T_f) corresponding to the type of `def_id` @ `args` and the
261/// super-type (T_g) corresponding to the `oblig_sig`. This subtyping is handled
262/// as akin to the code
263///
264///   T_f := (S1,...,Sn) -> S
265///   T_g := (T1,...,Tn) -> T
266///   T_f <: T_g
267///
268///  fn g(x1:T1,...,xn:Tn) -> T {
269///      f(x1,...,xn)
270///  }
271fn check_fn_subtyping(
272    infcx: &mut InferCtxt,
273    sub_sig: SubFn,
274    super_sig: &rty::PolyFnSig,
275    span: Span,
276) -> InferResult {
277    let mut infcx = infcx.branch();
278    let mut infcx = infcx.at(span);
279    let tcx = infcx.genv.tcx();
280
281    let super_sig = super_sig
282        .replace_bound_vars(
283            |_| rty::ReErased,
284            |sort, _, kind| Expr::fvar(infcx.define_bound_reft_var(sort, kind)),
285        )
286        .deeply_normalize(&mut infcx)?;
287
288    // 1. Unpack `T_g` input types
289    let actuals = super_sig
290        .inputs()
291        .iter()
292        .map(|ty| infcx.unpack(ty))
293        .collect_vec();
294
295    let mut env = TypeEnv::empty();
296    let actuals = unfold_local_ptrs(&mut infcx, &mut env, sub_sig.as_ref(), &actuals)?;
297    let actuals = infer_under_mut_ref_hack(&mut infcx, &actuals[..], sub_sig.as_ref());
298
299    let output = infcx.ensure_resolved_evars(|infcx| {
300        // 2. Fresh names for `T_f` refine-params / Instantiate fn_def_sig and normalize it
301        // in subtyping_mono, skip next two steps...
302        let sub_sig = match sub_sig {
303            SubFn::Poly(def_id, early_sig, sub_args) => {
304                let refine_args = infcx.instantiate_refine_args(def_id, &sub_args)?;
305                early_sig.instantiate(tcx, &sub_args, &refine_args)
306            }
307            SubFn::Mono(sig) => sig,
308        };
309        // ... jump right here.
310        let sub_sig = sub_sig
311            .replace_bound_vars(
312                |_| rty::ReErased,
313                |sort, mode, _| infcx.fresh_infer_var(sort, mode),
314            )
315            .deeply_normalize(infcx)?;
316
317        // 3. INPUT subtyping (g-input <: f-input)
318        for requires in super_sig.requires() {
319            infcx.assume_pred(requires);
320        }
321        infcx.check_pred(
322            Expr::implies(super_sig.no_panic(), sub_sig.no_panic()),
323            ConstrReason::Subtype(SubtypeReason::Input),
324        );
325        for (actual, formal) in iter::zip(actuals, sub_sig.inputs()) {
326            let reason = ConstrReason::Subtype(SubtypeReason::Input);
327            infcx.subtyping_with_env(&mut env, &actual, formal, reason)?;
328        }
329        // we check the requires AFTER the actual-formal subtyping as the above may unfold stuff in
330        // the actuals
331        for requires in sub_sig.requires() {
332            let reason = ConstrReason::Subtype(SubtypeReason::Requires);
333            infcx.check_pred(requires, reason);
334        }
335
336        Ok(sub_sig.output())
337    })?;
338
339    let output = infcx
340        .fully_resolve_evars(&output)
341        .replace_bound_refts_with(|sort, _, kind| {
342            Expr::fvar(infcx.define_bound_reft_var(sort, kind))
343        });
344
345    // 4. OUTPUT subtyping (f_out <: g_out)
346    infcx.ensure_resolved_evars(|infcx| {
347        let super_output = super_sig
348            .output()
349            .replace_bound_refts_with(|sort, mode, _| infcx.fresh_infer_var(sort, mode));
350        let reason = ConstrReason::Subtype(SubtypeReason::Output);
351        infcx.subtyping(&output.ret, &super_output.ret, reason)?;
352
353        // 6. Update state with Output "ensures" and check super ensures
354        env.assume_ensures(infcx, &output.ensures, span);
355        fold_local_ptrs(infcx, &mut env, span)?;
356        env.check_ensures(
357            infcx,
358            &super_output.ensures,
359            ConstrReason::Subtype(SubtypeReason::Ensures),
360        )
361    })
362}
363
364/// Trait subtyping check, which makes sure that the type for an impl method (def_id)
365/// is a subtype of the corresponding trait method.
366pub(crate) fn trait_impl_subtyping<'genv, 'tcx>(
367    genv: GlobalEnv<'genv, 'tcx>,
368    def_id: LocalDefId,
369    opts: InferOpts,
370    span: Span,
371) -> InferResult<Option<InferCtxtRoot<'genv, 'tcx>>> {
372    let tcx = genv.tcx();
373
374    // Skip the check if this is not an impl method
375    let Some((impl_trait_ref, trait_method_id)) = find_trait_item(genv, def_id)? else {
376        return Ok(None);
377    };
378    let impl_method_id = def_id.to_def_id();
379    // Skip the check if either the trait-method or the impl-method are marked as `trusted_impl`
380    if genv.has_trusted_impl(trait_method_id) || genv.has_trusted_impl(impl_method_id) {
381        return Ok(None);
382    }
383
384    let impl_id = tcx.impl_of_assoc(impl_method_id).unwrap();
385    let impl_method_args = GenericArg::identity_for_item(genv, impl_method_id)?;
386    let trait_method_args = impl_method_args.rebase_onto(&tcx, impl_id, &impl_trait_ref.args);
387    let trait_refine_args = RefineArgs::identity_for_item(genv, trait_method_id)?;
388
389    let rustc_infcx = genv
390        .tcx()
391        .infer_ctxt()
392        .with_next_trait_solver(true)
393        .build(TypingMode::non_body_analysis());
394
395    let mut root_ctxt = genv
396        .infcx_root(&rustc_infcx, opts)
397        .with_const_generics(impl_id)?
398        .with_refinement_generics(trait_method_id, &trait_method_args)?
399        .build()?;
400
401    let mut infcx = root_ctxt.infcx(impl_method_id, &rustc_infcx);
402
403    let trait_fn_sig =
404        genv.fn_sig(trait_method_id)?
405            .instantiate(tcx, &trait_method_args, &trait_refine_args);
406    let impl_sig = genv.fn_sig(impl_method_id)?;
407    let sub_sig = SubFn::Poly(impl_method_id, impl_sig, impl_method_args);
408
409    check_fn_subtyping(&mut infcx, sub_sig, &trait_fn_sig, span)?;
410    Ok(Some(root_ctxt))
411}
412
413fn find_trait_item(
414    genv: GlobalEnv<'_, '_>,
415    def_id: LocalDefId,
416) -> QueryResult<Option<(rty::TraitRef, DefId)>> {
417    let tcx = genv.tcx();
418    let def_id = def_id.to_def_id();
419    if let Some(impl_id) = tcx.trait_impl_of_assoc(def_id) {
420        let impl_trait_ref = genv.impl_trait_ref(impl_id)?.instantiate_identity();
421        let trait_item_id = tcx.associated_item(def_id).trait_item_def_id().unwrap();
422        return Ok(Some((impl_trait_ref, trait_item_id)));
423    }
424    Ok(None)
425}
426
427/// Temporarily (around a function call) convert an `&mut` to an `&strg` to allow for the call to be
428/// checked. This is done by unfolding the `&mut` into a local pointer at the call-site and then
429/// folding the pointer back into the `&mut` upon return.
430/// See also [`fold_local_ptrs`].
431///
432/// ```text
433///             unpack(T) = T'
434/// ---------------------------------------[local-unfold]
435/// Γ ; &mut T => Γ, l:[<: T] T' ; ptr(l)
436/// ```
437fn unfold_local_ptrs(
438    infcx: &mut InferCtxt,
439    env: &mut TypeEnv,
440    fn_sig: &PolyFnSig,
441    actuals: &[Ty],
442) -> InferResult<Vec<Ty>> {
443    // We *only* need to know whether each input is a &strg or not
444    let fn_sig = fn_sig.skip_binder_ref();
445    let mut tys = vec![];
446    for (actual, input) in izip!(actuals, fn_sig.inputs()) {
447        let actual = if let (
448            TyKind::Indexed(BaseTy::Ref(re, bound, Mutability::Mut), _),
449            TyKind::StrgRef(_, _, _),
450        ) = (actual.kind(), input.kind())
451        {
452            let loc = env.unfold_local_ptr(infcx, bound)?;
453            let path1 = Path::new(loc, rty::List::empty());
454            Ty::ptr(PtrKind::Mut(*re), path1)
455        } else {
456            actual.clone()
457        };
458        tys.push(actual);
459    }
460    Ok(tys)
461}
462
463/// Fold local pointers implements roughly a rule like the following (for all local pointers)
464/// that converts the local pointers created via [`unfold_local_ptrs`] back into `&mut`.
465///
466/// ```text
467///       T1 <: T2
468/// --------------------- [local-fold]
469/// Γ, l:[<: T2] T1 => Γ
470/// ```
471fn fold_local_ptrs(infcx: &mut InferCtxt, env: &mut TypeEnv, span: Span) -> InferResult {
472    let mut at = infcx.at(span);
473    env.fold_local_ptrs(&mut at)
474}
475
476fn promoted_fn_sig(ty: &Ty) -> PolyFnSig {
477    let safety = rustc_hir::Safety::Safe;
478    let abi = rustc_abi::ExternAbi::Rust;
479    let requires = rty::List::empty();
480    let inputs = rty::List::empty();
481    let output =
482        Binder::bind_with_vars(FnOutput::new(ty.clone(), rty::List::empty()), rty::List::empty());
483    let fn_sig = crate::rty::FnSig::new(safety, abi, requires, inputs, output, Expr::tt(), false);
484    PolyFnSig::bind_with_vars(fn_sig, crate::rty::List::empty())
485}
486
487impl<'ck, 'genv, 'tcx, M: Mode> Checker<'ck, 'genv, 'tcx, M> {
488    fn new(
489        genv: GlobalEnv<'genv, 'tcx>,
490        checker_id: CheckerId,
491        inherited: Inherited<'ck, M>,
492        body: &'ck Body<'tcx>,
493        fn_sig: FnSig,
494        promoted: &'ck IndexSlice<Promoted, Ty>,
495    ) -> QueryResult<Self> {
496        let root_id = checker_id.root_id();
497
498        let resume_ty = if let CheckerId::DefId(def_id) = checker_id
499            && genv.tcx().is_coroutine(def_id.to_def_id())
500        {
501            Some(fn_sig.inputs()[1].clone())
502        } else {
503            None
504        };
505
506        let bb_len = body.basic_blocks.len();
507        Ok(Self {
508            checker_id,
509            genv,
510            inherited,
511            body,
512            resume_ty,
513            visited: DenseBitSet::new_empty(bb_len),
514            fn_sig,
515            markers: IndexVec::from_fn_n(|_| None, bb_len),
516            queue: WorkQueue::empty(bb_len, &body.dominator_order_rank),
517            default_refiner: Refiner::default_for_item(genv, root_id.to_def_id())?,
518            promoted,
519        })
520    }
521
522    fn check_body(
523        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
524        checker_id: CheckerId,
525        inherited: Inherited<'ck, M>,
526        body: &'ck Body<'tcx>,
527        poly_sig: PolyFnSig,
528        promoted: &'ck IndexSlice<Promoted, Ty>,
529    ) -> Result {
530        let span = body.span();
531
532        let fn_sig = poly_sig
533            .replace_bound_vars(
534                |_| rty::ReErased,
535                |sort, _, kind| {
536                    let name = infcx.define_bound_reft_var(sort, kind);
537                    Expr::fvar(name)
538                },
539            )
540            .deeply_normalize(&mut infcx.at(span))
541            .with_span(span)?;
542        let mut env = TypeEnv::new(infcx, body, &fn_sig);
543
544        let mut ck = Checker::new(infcx.genv, checker_id, inherited, body, fn_sig, promoted)
545            .with_span(span)?;
546        ck.check_ghost_statements_at(infcx, &mut env, Point::FunEntry, span)?;
547
548        ck.check_goto(infcx.branch(), env, body.span(), START_BLOCK)?;
549
550        while let Some(bb) = ck.queue.pop() {
551            let visited = ck.visited.contains(bb);
552
553            if visited {
554                M::clear(&mut ck, bb);
555            }
556
557            let marker = ck.marker_at_dominator(bb);
558            let mut infcx = infcx.move_to(marker, visited);
559            let mut env = M::enter_basic_block(&mut ck, &mut infcx, bb);
560            env.unpack(&mut infcx);
561            ck.check_basic_block(infcx, env, bb)?;
562        }
563        Ok(())
564    }
565
566    /// Assign a template with fresh kvars to each promoted constant in `body_root`.
567    fn promoted_tys(
568        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
569        def_id: LocalDefId,
570        body_root: &BodyRoot<'tcx>,
571    ) -> QueryResult<IndexVec<Promoted, Ty>> {
572        let hole_refiner = Refiner::with_holes(infcx.genv, def_id.into())?;
573
574        body_root
575            .promoted
576            .iter()
577            .map(|body| {
578                Ok(body
579                    .return_ty()
580                    .refine(&hole_refiner)?
581                    .replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind)))
582            })
583            .collect()
584    }
585
586    fn run(
587        mut infcx: InferCtxt<'_, 'genv, 'tcx>,
588        def_id: LocalDefId,
589        mut inherited: Inherited<'_, M>,
590        poly_sig: PolyFnSig,
591    ) -> Result {
592        let genv = infcx.genv;
593        let span = genv.tcx().def_span(def_id);
594        let body_root = genv.mir(def_id).with_span(span)?;
595
596        // 1. Generate templates for promoted consts
597        let promoted_tys = Self::promoted_tys(&mut infcx, def_id, &body_root).with_span(span)?;
598
599        // 2. Check the body of all promoted
600        for (promoted, ty) in promoted_tys.iter_enumerated() {
601            let body = &body_root.promoted[promoted];
602            let poly_sig = promoted_fn_sig(ty);
603            Checker::check_body(
604                &mut infcx,
605                CheckerId::Promoted(def_id, promoted),
606                inherited.reborrow(),
607                body,
608                poly_sig,
609                &promoted_tys,
610            )?;
611        }
612
613        // 3. Check the main body
614        Checker::check_body(
615            &mut infcx,
616            CheckerId::DefId(def_id),
617            inherited,
618            &body_root.body,
619            poly_sig,
620            &promoted_tys,
621        )
622    }
623
624    fn check_basic_block(
625        &mut self,
626        mut infcx: InferCtxt<'_, 'genv, 'tcx>,
627        mut env: TypeEnv,
628        bb: BasicBlock,
629    ) -> Result {
630        dbg::basic_block_start!(bb, infcx, env);
631
632        self.visited.insert(bb);
633        let data = &self.body.basic_blocks[bb];
634        let mut last_stmt_span = None;
635        let mut location = Location { block: bb, statement_index: 0 };
636        for stmt in &data.statements {
637            let span = stmt.source_info.span;
638            self.check_ghost_statements_at(
639                &mut infcx,
640                &mut env,
641                Point::BeforeLocation(location),
642                span,
643            )?;
644            bug::track_span(span, || {
645                dbg::statement!("start", stmt, &infcx, &env, span, &self);
646                self.check_statement(&mut infcx, &mut env, stmt)?;
647                dbg::statement!("end", stmt, &infcx, &env, span, &self);
648                Ok(())
649            })?;
650            if !stmt.is_nop() {
651                last_stmt_span = Some(span);
652            }
653            location = location.successor_within_block();
654        }
655
656        if let Some(terminator) = &data.terminator {
657            let span = terminator.source_info.span;
658            self.check_ghost_statements_at(
659                &mut infcx,
660                &mut env,
661                Point::BeforeLocation(location),
662                span,
663            )?;
664
665            bug::track_span(span, || {
666                dbg::terminator!("start", terminator, infcx, env);
667
668                let successors = self.check_terminator(
669                    &mut infcx,
670                    &mut env,
671                    terminator,
672                    location,
673                    last_stmt_span,
674                )?;
675                dbg::terminator!("end", terminator, infcx, env);
676
677                self.markers[bb] = Some(infcx.marker());
678                let term_span = last_stmt_span.unwrap_or(span);
679                self.check_successors(infcx, env, bb, term_span, successors)
680            })?;
681        }
682        Ok(())
683    }
684
685    fn check_assign_ty(
686        &mut self,
687        infcx: &mut InferCtxt,
688        env: &mut TypeEnv,
689        place: &Place,
690        ty: Ty,
691        span: Span,
692    ) -> InferResult {
693        let ty = infcx.hoister(true).hoist(&ty);
694        env.assign(&mut infcx.at(span), place, ty)
695    }
696
697    fn check_statement(
698        &mut self,
699        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
700        env: &mut TypeEnv,
701        stmt: &Statement<'tcx>,
702    ) -> Result {
703        let stmt_span = stmt.source_info.span;
704        match &stmt.kind {
705            StatementKind::Assign(place, rvalue) => {
706                let ty = self.check_rvalue(infcx, env, stmt_span, rvalue)?;
707                self.check_assign_ty(infcx, env, place, ty, stmt_span)
708                    .with_span(stmt_span)?;
709            }
710            StatementKind::SetDiscriminant { .. } => {
711                // TODO(nilehmann) double check here that the place is unfolded to
712                // the correct variant. This should be guaranteed by rustc
713            }
714            StatementKind::FakeRead(_) => {
715                // TODO(nilehmann) fake reads should be folding points
716            }
717            StatementKind::AscribeUserType(_, _) => {
718                // User ascriptions affect nll, but no refinement type checking.
719                // Maybe we can use this to associate refinement type to locals.
720            }
721            StatementKind::PlaceMention(_) => {
722                // Place mentions are a no-op used to detect uses of unsafe that would
723                // otherwise be optimized away.
724            }
725            StatementKind::Nop => {}
726            StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(op)) => {
727                // Currently, we only have the `assume` intrinsic, which if we're to trust rustc should be a NOP.
728                // TODO: There may be a use-case to actually "assume" the bool index associated with the operand,
729                // i.e. to strengthen the `rcx` / `env` with the assumption that the bool-index is in fact `true`...
730                let _ = self
731                    .check_operand(infcx, env, stmt_span, op)
732                    .with_span(stmt_span)?;
733            }
734        }
735        Ok(())
736    }
737
738    fn is_exit_block(&self, bb: BasicBlock) -> bool {
739        let data = &self.body.basic_blocks[bb];
740        let is_no_op = data.statements.iter().all(Statement::is_nop);
741        let is_ret = match &data.terminator {
742            None => false,
743            Some(term) => term.is_return(),
744        };
745        is_no_op && is_ret
746    }
747
748    /// For `check_terminator`, the output `Vec<BasicBlock, Guard>` denotes,
749    /// - `BasicBlock` "successors" of the current terminator, and
750    /// - `Guard` are extra control information from, e.g. the `SwitchInt` (or `Assert`) you can assume when checking the corresponding successor.
751    fn check_terminator(
752        &mut self,
753        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
754        env: &mut TypeEnv,
755        terminator: &Terminator<'tcx>,
756        location: Location,
757        last_stmt_span: Option<Span>,
758    ) -> Result<Vec<(BasicBlock, Guard)>> {
759        let source_info = terminator.source_info;
760        let terminator_span = source_info.span;
761        match &terminator.kind {
762            TerminatorKind::Return => {
763                self.check_ret(infcx, env, last_stmt_span.unwrap_or(terminator_span))?;
764                Ok(vec![])
765            }
766            TerminatorKind::Unreachable => Ok(vec![]),
767            TerminatorKind::CoroutineDrop => Ok(vec![]),
768            TerminatorKind::Goto { target } => Ok(vec![(*target, Guard::None)]),
769            TerminatorKind::Yield { resume, resume_arg, .. } => {
770                if let Some(resume_ty) = self.resume_ty.clone() {
771                    self.check_assign_ty(infcx, env, resume_arg, resume_ty, terminator_span)
772                        .with_span(terminator_span)?;
773                } else {
774                    bug!("yield in non-generator function");
775                }
776                Ok(vec![(*resume, Guard::None)])
777            }
778            TerminatorKind::SwitchInt { discr, targets } => {
779                let discr_ty = self
780                    .check_operand(infcx, env, terminator_span, discr)
781                    .with_span(terminator_span)?;
782                if discr_ty.is_integral() || discr_ty.is_bool() || discr_ty.is_char() {
783                    Ok(Self::check_if(&discr_ty, targets))
784                } else {
785                    Ok(self.check_match(infcx, env, &discr_ty, targets, terminator_span))
786                }
787            }
788            TerminatorKind::Call { kind, args, destination, target, .. } => {
789                let actuals = self
790                    .check_operands(infcx, env, terminator_span, args)
791                    .with_span(terminator_span)?;
792                let ret = match kind {
793                    mir::CallKind::FnDef { resolved_id, resolved_args, .. } => {
794                        let fn_sig = self.genv.fn_sig(*resolved_id).with_span(terminator_span)?;
795                        let generic_args = instantiate_args_for_fun_call(
796                            self.genv,
797                            self.checker_id.root_id().to_def_id(),
798                            *resolved_id,
799                            &resolved_args.lowered,
800                        )
801                        .with_span(terminator_span)?;
802                        self.check_call(
803                            infcx,
804                            env,
805                            terminator_span,
806                            Some(location),
807                            Some(*resolved_id),
808                            fn_sig,
809                            &generic_args,
810                            &actuals,
811                        )?
812                        .output
813                    }
814                    mir::CallKind::FnPtr { operand, .. } => {
815                        let ty = self
816                            .check_operand(infcx, env, terminator_span, operand)
817                            .with_span(terminator_span)?;
818                        if let TyKind::Indexed(BaseTy::FnPtr(fn_sig), _) = infcx.unpack(&ty).kind()
819                        {
820                            self.check_call(
821                                infcx,
822                                env,
823                                terminator_span,
824                                Some(location),
825                                None,
826                                EarlyBinder(fn_sig.clone()),
827                                &[],
828                                &actuals,
829                            )?
830                            .output
831                        } else {
832                            bug!("TODO: fnptr call {ty:?}")
833                        }
834                    }
835                };
836
837                let name = destination.name(&self.body.local_names);
838                let ret = infcx.unpack_at_name(name, &ret);
839                infcx.assume_invariants(&ret);
840
841                env.assign(&mut infcx.at(terminator_span), destination, ret)
842                    .with_span(terminator_span)?;
843
844                if let Some(target) = target {
845                    Ok(vec![(*target, Guard::None)])
846                } else {
847                    Ok(vec![])
848                }
849            }
850            TerminatorKind::Assert { cond, expected, target, msg } => {
851                Ok(vec![(
852                    *target,
853                    self.check_assert(infcx, env, terminator_span, cond, *expected, msg)
854                        .with_span(terminator_span)?,
855                )])
856            }
857            TerminatorKind::Drop { place, target, .. } => {
858                let _ = env.move_place(&mut infcx.at(terminator_span), place);
859                Ok(vec![(*target, Guard::None)])
860            }
861            TerminatorKind::FalseEdge { real_target, .. } => Ok(vec![(*real_target, Guard::None)]),
862            TerminatorKind::FalseUnwind { real_target, .. } => {
863                Ok(vec![(*real_target, Guard::None)])
864            }
865            TerminatorKind::UnwindResume => bug!("TODO: implement checking of cleanup code"),
866        }
867    }
868
869    fn check_ret(
870        &mut self,
871        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
872        env: &mut TypeEnv,
873        span: Span,
874    ) -> Result {
875        let obligations = infcx
876            .at(span)
877            .ensure_resolved_evars(|infcx| {
878                let ret_place_ty = env.lookup_place(infcx, Place::RETURN)?;
879                let output = self
880                    .fn_sig
881                    .output
882                    .replace_bound_refts_with(|sort, mode, _| infcx.fresh_infer_var(sort, mode));
883                let obligations =
884                    infcx.subtyping_with_env(env, &ret_place_ty, &output.ret, ConstrReason::Ret)?;
885
886                env.check_ensures(infcx, &output.ensures, ConstrReason::Ret)?;
887
888                Ok(obligations)
889            })
890            .with_span(span)?;
891
892        self.check_coroutine_obligations(infcx, obligations)
893    }
894
895    #[expect(clippy::too_many_arguments)]
896    fn check_call(
897        &mut self,
898        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
899        env: &mut TypeEnv,
900        span: Span,
901        location: Option<Location>,
902        callee_def_id: Option<DefId>,
903        fn_sig: EarlyBinder<PolyFnSig>,
904        generic_args: &[GenericArg],
905        actuals: &[Ty],
906    ) -> Result<ResolvedCall> {
907        let genv = self.genv;
908        let tcx = genv.tcx();
909        // Recover the resolved callee `NodeKey` from the call graph by the call-site location,
910        // then query the no-panic spec map.
911        let body_def_id = match self.checker_id {
912            CheckerId::DefId(def_id) => Some(def_id.to_def_id()),
913            CheckerId::Promoted(..) => None,
914        };
915        if let Some(callee_def_id) = callee_def_id {
916            dbg::call!(genv, callee_def_id, body_def_id, span);
917        }
918
919        let actuals =
920            unfold_local_ptrs(infcx, env, fn_sig.skip_binder_ref(), actuals).with_span(span)?;
921        let actuals = infer_under_mut_ref_hack(infcx, &actuals, fn_sig.skip_binder_ref());
922        infcx.push_evar_scope();
923
924        // Replace holes in generic arguments with fresh inference variables
925        let generic_args = infcx.instantiate_generic_args(generic_args);
926
927        // Generate fresh inference variables for refinement arguments
928        let early_refine_args = match callee_def_id {
929            Some(callee_def_id) => {
930                infcx
931                    .instantiate_refine_args(callee_def_id, &generic_args)
932                    .with_span(span)?
933            }
934            None => rty::List::empty(),
935        };
936
937        let clauses = match callee_def_id {
938            Some(callee_def_id) => {
939                genv.predicates_of(callee_def_id)
940                    .with_span(span)?
941                    .predicates()
942                    .instantiate(tcx, &generic_args, &early_refine_args)
943            }
944            None => crate::rty::List::empty(),
945        };
946
947        let (clauses, fn_clauses) = Clause::split_off_fn_trait_clauses(self.genv, &clauses);
948        infcx
949            .at(span)
950            .check_non_closure_clauses(&clauses, ConstrReason::Call)
951            .with_span(span)?;
952
953        for fn_trait_pred in &fn_clauses {
954            self.check_fn_trait_clause(infcx, fn_trait_pred, span)?;
955        }
956
957        // Instantiate function signature and normalize it
958        let late_refine_args = vec![];
959        let fn_sig = fn_sig
960            .instantiate(tcx, &generic_args, &early_refine_args)
961            .replace_bound_vars(
962                |_| rty::ReErased,
963                |sort, mode, _| infcx.fresh_infer_var(sort, mode),
964            );
965
966        let fn_sig = fn_sig
967            .deeply_normalize(&mut infcx.at(span))
968            .with_span(span)?;
969
970        let mut at = infcx.at(span);
971
972        if let Some(callee_def_id) = callee_def_id
973            && genv.def_kind(callee_def_id).is_fn_like()
974        {
975            let callee_no_panic = fn_sig.no_panic();
976
977            let callee_inferred_spec = body_def_id
978                .zip(location)
979                .and_then(|(body_def_id, location)| {
980                    genv.call_graph().resolved_callee(body_def_id, location)
981                })
982                .map(|key| genv.inferred_no_panic_key(key))
983                .unwrap_or(PanicSpec::MightPanic(PanicReason::NotInCallGraph));
984
985            let inferred_panic_expr = if callee_inferred_spec == PanicSpec::WillNotPanic {
986                Expr::tt()
987            } else {
988                Expr::ff()
989            };
990
991            at.check_pred(
992                Expr::implies(
993                    self.fn_sig.no_panic(),
994                    Expr::or(callee_no_panic, inferred_panic_expr),
995                ),
996                ConstrReason::NoPanic(callee_def_id, callee_inferred_spec),
997            );
998        }
999
1000        // Check requires predicates
1001        for requires in fn_sig.requires() {
1002            at.check_pred(requires, ConstrReason::Call);
1003        }
1004
1005        // Check arguments
1006        for (actual, formal) in iter::zip(actuals, fn_sig.inputs()) {
1007            at.subtyping_with_env(env, &actual, formal, ConstrReason::Call)
1008                .with_span(span)?;
1009        }
1010
1011        infcx.pop_evar_scope().with_span(span)?;
1012        env.fully_resolve_evars(infcx);
1013
1014        let output = infcx
1015            .fully_resolve_evars(&fn_sig.output)
1016            .replace_bound_refts_with(|sort, _, kind| {
1017                Expr::fvar(infcx.define_bound_reft_var(sort, kind))
1018            });
1019
1020        env.assume_ensures(infcx, &output.ensures, span);
1021        fold_local_ptrs(infcx, env, span).with_span(span)?;
1022
1023        Ok(ResolvedCall {
1024            output: output.ret,
1025            _early_args: early_refine_args
1026                .into_iter()
1027                .map(|arg| infcx.fully_resolve_evars(arg))
1028                .collect(),
1029            _late_args: late_refine_args
1030                .into_iter()
1031                .map(|arg| infcx.fully_resolve_evars(&arg))
1032                .collect(),
1033        })
1034    }
1035
1036    fn check_coroutine_obligations(
1037        &mut self,
1038        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1039        obligs: Vec<Binder<CoroutineObligPredicate>>,
1040    ) -> Result {
1041        for oblig in obligs {
1042            // FIXME(nilehmann) we shouldn't be skipping this binder
1043            let oblig = oblig.skip_binder();
1044
1045            #[expect(clippy::disallowed_methods, reason = "coroutines cannot be extern speced")]
1046            let def_id = oblig.def_id.expect_local();
1047            let span = self.genv.tcx().def_span(def_id);
1048            let body = self.genv.mir(def_id).with_span(span)?;
1049            Checker::run(
1050                infcx.change_item(def_id, &body.infcx),
1051                def_id,
1052                self.inherited.reborrow(),
1053                oblig.to_poly_fn_sig(),
1054            )?;
1055        }
1056        Ok(())
1057    }
1058
1059    fn find_self_ty_fn_sig(
1060        &self,
1061        self_ty: rustc_middle::ty::Ty<'tcx>,
1062        span: Span,
1063    ) -> Result<PolyFnSig> {
1064        let tcx = self.genv.tcx();
1065        let mut def_id = Some(self.checker_id.root_id().to_def_id());
1066        while let Some(did) = def_id {
1067            let generic_predicates = self
1068                .genv
1069                .predicates_of(did)
1070                .with_span(span)?
1071                .instantiate_identity();
1072            let predicates = generic_predicates.predicates;
1073
1074            for poly_fn_trait_pred in Clause::split_off_fn_trait_clauses(self.genv, &predicates).1 {
1075                if poly_fn_trait_pred.skip_binder_ref().self_ty.to_rustc(tcx) == self_ty {
1076                    return Ok(poly_fn_trait_pred.map(|fn_trait_pred| fn_trait_pred.fndef_sig()));
1077                }
1078            }
1079            // Continue to the parent if we didn't find a match
1080            def_id = generic_predicates.parent;
1081        }
1082
1083        span_bug!(
1084            span,
1085            "cannot find self_ty_fn_sig for {:?} with self_ty = {self_ty:?}",
1086            self.checker_id
1087        );
1088    }
1089
1090    fn check_fn_trait_clause(
1091        &mut self,
1092        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1093        poly_fn_trait_pred: &Binder<FnTraitPredicate>,
1094        span: Span,
1095    ) -> Result {
1096        let self_ty = poly_fn_trait_pred
1097            .skip_binder_ref()
1098            .self_ty
1099            .as_bty_skipping_existentials();
1100        let oblig_sig = poly_fn_trait_pred.map_ref(|fn_trait_pred| fn_trait_pred.fndef_sig());
1101        match self_ty {
1102            Some(BaseTy::Closure(def_id, _, _, _)) => {
1103                let Some(poly_sig) = self.inherited.closures.get(def_id).cloned() else {
1104                    span_bug!(span, "missing template for closure {def_id:?}");
1105                };
1106                check_fn_subtyping(infcx, SubFn::Mono(poly_sig.clone()), &oblig_sig, span)
1107                    .with_span(span)?;
1108            }
1109            Some(BaseTy::FnDef(def_id, args)) => {
1110                // Generates "function subtyping" obligations between the (super-type) `oblig_sig` in the `fn_trait_pred`
1111                // and the (sub-type) corresponding to the signature of `def_id + args`.
1112                // See `tests/neg/surface/fndef00.rs`
1113                let sub_sig = self.genv.fn_sig(def_id).with_span(span)?;
1114                check_fn_subtyping(
1115                    infcx,
1116                    SubFn::Poly(*def_id, sub_sig, args.clone()),
1117                    &oblig_sig,
1118                    span,
1119                )
1120                .with_span(span)?;
1121            }
1122            Some(BaseTy::FnPtr(sub_sig)) => {
1123                check_fn_subtyping(infcx, SubFn::Mono(sub_sig.clone()), &oblig_sig, span)
1124                    .with_span(span)?;
1125            }
1126
1127            // Some(self_ty) => {
1128            Some(self_ty @ BaseTy::Param(_)) => {
1129                // Step 1. Find matching clause and turn it into a FnSig
1130                let tcx = self.genv.tcx();
1131                let self_ty = self_ty.to_rustc(tcx);
1132                let sub_sig = self.find_self_ty_fn_sig(self_ty, span)?;
1133                // Step 2. Issue the subtyping
1134                check_fn_subtyping(infcx, SubFn::Mono(sub_sig), &oblig_sig, span)
1135                    .with_span(span)?;
1136            }
1137            _ => {}
1138        }
1139        Ok(())
1140    }
1141
1142    fn check_assert(
1143        &mut self,
1144        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1145        env: &mut TypeEnv,
1146        terminator_span: Span,
1147        cond: &Operand<'tcx>,
1148        expected: bool,
1149        msg: &AssertKind,
1150    ) -> InferResult<Guard> {
1151        let ty = self.check_operand(infcx, env, terminator_span, cond)?;
1152        let TyKind::Indexed(BaseTy::Bool, idx) = ty.kind() else {
1153            tracked_span_bug!("unexpected ty `{ty:?}`");
1154        };
1155        let pred = if expected { idx.clone() } else { idx.not() };
1156
1157        let msg = match msg {
1158            AssertKind::DivisionByZero => "possible division by zero",
1159            AssertKind::BoundsCheck => "possible out-of-bounds access",
1160            AssertKind::RemainderByZero => "possible remainder with a divisor of zero",
1161            AssertKind::Overflow(mir::BinOp::Div) => "possible division with overflow",
1162            AssertKind::Overflow(mir::BinOp::Rem) => "possible reminder with overflow",
1163            AssertKind::Overflow(_) => return Ok(Guard::Pred(pred)),
1164        };
1165        infcx
1166            .at(terminator_span)
1167            .check_pred(&pred, ConstrReason::Assert(msg));
1168        Ok(Guard::Pred(pred))
1169    }
1170
1171    /// Checks conditional branching as in a `match` statement. [`SwitchTargets`](https://doc.rust-lang.org/nightly/nightly-rustc/stable_mir/mir/struct.SwitchTargets.html) contains a list of branches - the exact bit value which is being compared and the block to jump to. Using the conditionals, each branch can be checked using the new control flow information.
1172    /// See <https://github.com/flux-rs/flux/pull/840#discussion_r1786543174>
1173    fn check_if(discr_ty: &Ty, targets: &SwitchTargets) -> Vec<(BasicBlock, Guard)> {
1174        let mk = |bits| {
1175            match discr_ty.kind() {
1176                TyKind::Indexed(BaseTy::Bool, idx) => {
1177                    if bits == 0 {
1178                        idx.not()
1179                    } else {
1180                        idx.clone()
1181                    }
1182                }
1183                TyKind::Indexed(bty @ (BaseTy::Int(_) | BaseTy::Uint(_) | BaseTy::Char), idx) => {
1184                    Expr::eq(idx.clone(), Expr::from_bits(bty, bits))
1185                }
1186                _ => tracked_span_bug!("unexpected discr_ty {:?}", discr_ty),
1187            }
1188        };
1189
1190        let mut successors = vec![];
1191
1192        for (bits, bb) in targets.iter() {
1193            successors.push((bb, Guard::Pred(mk(bits))));
1194        }
1195        let otherwise = Expr::and_from_iter(targets.iter().map(|(bits, _)| mk(bits).not()));
1196        successors.push((targets.otherwise(), Guard::Pred(otherwise)));
1197
1198        successors
1199    }
1200
1201    fn check_match(
1202        &mut self,
1203        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1204        env: &mut TypeEnv,
1205        discr_ty: &Ty,
1206        targets: &SwitchTargets,
1207        span: Span,
1208    ) -> Vec<(BasicBlock, Guard)> {
1209        let (adt_def, place) = discr_ty.expect_discr();
1210        let idx = if let Ok(ty) = env.lookup_place(&mut infcx.at(span), place)
1211            && let TyKind::Indexed(_, idx) = ty.kind()
1212        {
1213            Some(idx.clone())
1214        } else {
1215            None
1216        };
1217
1218        let mut successors = vec![];
1219        let mut remaining: FxHashMap<u128, VariantIdx> = adt_def
1220            .discriminants()
1221            .map(|(idx, discr)| (discr, idx))
1222            .collect();
1223        for (bits, bb) in targets.iter() {
1224            let variant_idx = remaining
1225                .remove(&bits)
1226                .expect("value doesn't correspond to any variant");
1227            successors.push((bb, Guard::Match(place.clone(), variant_idx)));
1228        }
1229        let guard = if remaining.len() == 1 {
1230            // If there's only one variant left, we know for sure that this is the one, so can force an unfold
1231            let (_, variant_idx) = remaining
1232                .into_iter()
1233                .next()
1234                .unwrap_or_else(|| tracked_span_bug!());
1235            Guard::Match(place.clone(), variant_idx)
1236        } else if adt_def.sort_def().is_reflected()
1237            && let Some(idx) = idx
1238        {
1239            // If there's more than one variant left, we can only assume the `is_ctor` holds for one of them
1240            let mut cases = vec![];
1241            for (_, variant_idx) in remaining {
1242                let did = adt_def.did();
1243                cases.push(rty::Expr::is_ctor(did, variant_idx, idx.clone()));
1244            }
1245            Guard::Pred(Expr::or_from_iter(cases))
1246        } else {
1247            Guard::None
1248        };
1249        successors.push((targets.otherwise(), guard));
1250
1251        successors
1252    }
1253
1254    fn check_successors(
1255        &mut self,
1256        mut infcx: InferCtxt<'_, 'genv, 'tcx>,
1257        env: TypeEnv,
1258        from: BasicBlock,
1259        terminator_span: Span,
1260        successors: Vec<(BasicBlock, Guard)>,
1261    ) -> Result {
1262        for (target, guard) in successors {
1263            let mut infcx = infcx.branch();
1264            let mut env = env.clone();
1265            match guard {
1266                Guard::None => {}
1267                Guard::Pred(expr) => {
1268                    infcx.assume_pred(&expr);
1269                }
1270                Guard::Match(place, variant_idx) => {
1271                    env.downcast(&mut infcx.at(terminator_span), &place, variant_idx)
1272                        .with_span(terminator_span)?;
1273                }
1274            }
1275            self.check_ghost_statements_at(
1276                &mut infcx,
1277                &mut env,
1278                Point::Edge(from, target),
1279                terminator_span,
1280            )?;
1281            self.check_goto(infcx, env, terminator_span, target)?;
1282        }
1283        Ok(())
1284    }
1285
1286    fn check_goto(
1287        &mut self,
1288        mut infcx: InferCtxt<'_, 'genv, 'tcx>,
1289        mut env: TypeEnv,
1290        span: Span,
1291        target: BasicBlock,
1292    ) -> Result {
1293        if self.is_exit_block(target) {
1294            // We inline *exit basic blocks* (i.e., that just return) because this typically
1295            // gives us better a better error span.
1296            let mut location = Location { block: target, statement_index: 0 };
1297            for _ in &self.body.basic_blocks[target].statements {
1298                self.check_ghost_statements_at(
1299                    &mut infcx,
1300                    &mut env,
1301                    Point::BeforeLocation(location),
1302                    span,
1303                )?;
1304                location = location.successor_within_block();
1305            }
1306            self.check_ghost_statements_at(
1307                &mut infcx,
1308                &mut env,
1309                Point::BeforeLocation(location),
1310                span,
1311            )?;
1312            self.check_ret(&mut infcx, &mut env, span)
1313        } else if let Some(real_target) = self.is_dummy_join(target) {
1314            self.check_goto(infcx, env, span, real_target)
1315        } else if self.body.is_join_point(target) {
1316            if M::check_goto_join_point(self, infcx, env, span, target)? {
1317                self.queue.insert(target);
1318            }
1319            Ok(())
1320        } else {
1321            self.check_basic_block(infcx, env, target)
1322        }
1323    }
1324
1325    /// A dummy-join-block is a BasicBlock that has
1326    /// 1. MULTIPLE incoming edges,
1327    /// 2. SINGLE outgoing edge,
1328    /// 3. ONLY no-op statements, and
1329    /// 4. NO ghosts statements.
1330    ///
1331    /// (1) is because we want to avoid "spurious joins"
1332    /// but also, without it, we have problems with
1333    /// degenerate loops like (e.g. `const_generics/loop.rs`).
1334    ///
1335    /// We can "skip" such blocks and jump straight to
1336    /// the first (transitively reachable) non-dummy
1337    /// successor, aka the "real" successor, which allows
1338    /// us to avoid emitting KVars for spurious joins.
1339    fn is_dummy_join(&self, bb: BasicBlock) -> Option<BasicBlock> {
1340        if self.body.is_join_point(bb)
1341            && self.body.basic_blocks[bb]
1342                .statements
1343                .iter()
1344                .all(Statement::is_nop)
1345            && let Some(TerminatorKind::Goto { target: real_target }) = &self.body.basic_blocks[bb]
1346                .terminator
1347                .as_ref()
1348                .map(|terminator| &terminator.kind)
1349            && self.no_ghosts_at(bb, *real_target)
1350        {
1351            Some(*real_target)
1352        } else {
1353            None
1354        }
1355    }
1356
1357    fn no_ghosts_at(&self, bb: BasicBlock, real_target: BasicBlock) -> bool {
1358        let Some(ghosts) = self.inherited.ghost_stmts.get(&self.checker_id) else {
1359            return true;
1360        };
1361
1362        let mut res = ghosts
1363            .statements_at(Point::Edge(bb, real_target))
1364            .next()
1365            .is_none();
1366
1367        let mut location = Location { block: bb, statement_index: 0 };
1368        for _ in &self.body.basic_blocks[bb].statements {
1369            res = res
1370                && ghosts
1371                    .statements_at(Point::BeforeLocation(location))
1372                    .next()
1373                    .is_none();
1374            location = location.successor_within_block();
1375        }
1376        res = res
1377            && ghosts
1378                .statements_at(Point::BeforeLocation(location))
1379                .next()
1380                .is_none();
1381        res
1382    }
1383
1384    fn closure_template(
1385        &mut self,
1386        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1387        env: &mut TypeEnv,
1388        stmt_span: Span,
1389        args: &flux_rustc_bridge::ty::GenericArgs,
1390        operands: &[Operand<'tcx>],
1391    ) -> InferResult<(Vec<Ty>, PolyFnSig)> {
1392        let upvar_tys = self
1393            .check_operands(infcx, env, stmt_span, operands)?
1394            .into_iter()
1395            .map(|ty| {
1396                if let TyKind::Ptr(PtrKind::Mut(re), path) = ty.kind() {
1397                    env.ptr_to_ref(
1398                        &mut infcx.at(stmt_span),
1399                        ConstrReason::Other,
1400                        *re,
1401                        path,
1402                        PtrToRefBound::Infer,
1403                    )
1404                } else {
1405                    Ok(ty.clone())
1406                }
1407            })
1408            .try_collect_vec()?;
1409
1410        let closure_args = args.as_closure();
1411        let ty = closure_args.sig_as_fn_ptr_ty();
1412
1413        if let flux_rustc_bridge::ty::TyKind::FnPtr(poly_sig) = ty.kind() {
1414            let poly_sig = poly_sig.unpack_closure_sig();
1415            let poly_sig = self.refine_with_holes(&poly_sig)?;
1416            let poly_sig = poly_sig.hoist_input_binders();
1417            let poly_sig = poly_sig
1418                .replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind));
1419
1420            Ok((upvar_tys, poly_sig))
1421        } else {
1422            bug!("check_rvalue: closure: expected fn_ptr ty, found {ty:?} in {args:?}");
1423        }
1424    }
1425
1426    fn check_closure_body(
1427        &mut self,
1428        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1429        did: &DefId,
1430        upvar_tys: &[Ty],
1431        args: &flux_rustc_bridge::ty::GenericArgs,
1432        poly_sig: &PolyFnSig,
1433    ) -> Result {
1434        let genv = self.genv;
1435        let tcx = genv.tcx();
1436        #[expect(clippy::disallowed_methods, reason = "closures cannot be extern speced")]
1437        let closure_id = did.expect_local();
1438        let span = tcx.def_span(closure_id);
1439        let body = genv.mir(closure_id).with_span(span)?;
1440        let no_panic = self.genv.no_panic(*did);
1441        let closure_sig = rty::to_closure_sig(tcx, closure_id, upvar_tys, args, poly_sig, no_panic);
1442        Checker::run(
1443            infcx.change_item(closure_id, &body.infcx),
1444            closure_id,
1445            self.inherited.reborrow(),
1446            closure_sig,
1447        )
1448    }
1449
1450    fn check_rvalue_closure(
1451        &mut self,
1452        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1453        env: &mut TypeEnv,
1454        stmt_span: Span,
1455        did: &DefId,
1456        args: &flux_rustc_bridge::ty::GenericArgs,
1457        operands: &[Operand<'tcx>],
1458    ) -> Result<Ty> {
1459        // (1) Create the closure template
1460        let (upvar_tys, poly_sig) = self
1461            .closure_template(infcx, env, stmt_span, args, operands)
1462            .with_span(stmt_span)?;
1463        // (2) Check the closure body against the template
1464        self.check_closure_body(infcx, did, &upvar_tys, args, &poly_sig)?;
1465        // (3) "Save" the closure type in the `closures` map
1466        self.inherited.closures.insert(*did, poly_sig);
1467        // (4) Return the closure type
1468        let no_panic = self.genv.no_panic(*did);
1469        Ok(Ty::closure(*did, upvar_tys, args, no_panic))
1470    }
1471
1472    fn check_rvalue(
1473        &mut self,
1474        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1475        env: &mut TypeEnv,
1476        stmt_span: Span,
1477        rvalue: &Rvalue<'tcx>,
1478    ) -> Result<Ty> {
1479        let genv = self.genv;
1480        match rvalue {
1481            Rvalue::Use(operand) => {
1482                self.check_operand(infcx, env, stmt_span, operand)
1483                    .with_span(stmt_span)
1484            }
1485            Rvalue::Repeat(operand, c) => {
1486                let ty = self
1487                    .check_operand(infcx, env, stmt_span, operand)
1488                    .with_span(stmt_span)?;
1489                let arr_ty = ty
1490                    .with_holes()
1491                    .replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind));
1492                infcx
1493                    .at(stmt_span)
1494                    .subtyping_with_env(env, &ty, &arr_ty, ConstrReason::Other)
1495                    .with_span(stmt_span)?;
1496                Ok(Ty::array(arr_ty, c.clone()))
1497            }
1498            Rvalue::Ref(r, BorrowKind::Mut { .. }, place) => {
1499                env.borrow(&mut infcx.at(stmt_span), *r, Mutability::Mut, place)
1500                    .with_span(stmt_span)
1501            }
1502            Rvalue::Ref(r, BorrowKind::Shared | BorrowKind::Fake(..), place) => {
1503                env.borrow(&mut infcx.at(stmt_span), *r, Mutability::Not, place)
1504                    .with_span(stmt_span)
1505            }
1506
1507            Rvalue::RawPtr(mir::RawPtrKind::FakeForPtrMetadata, place) => {
1508                // see tests/tests/neg/surface/slice02.rs for what happens without unfolding here.
1509                env.unfold(infcx, place, stmt_span).with_span(stmt_span)?;
1510                let ty = env
1511                    .lookup_place(&mut infcx.at(stmt_span), place)
1512                    .with_span(stmt_span)?;
1513                let ty = BaseTy::RawPtrMetadata(ty).to_ty();
1514                Ok(ty)
1515            }
1516            Rvalue::RawPtr(kind, place) => {
1517                // ignore any refinements on the type stored at place
1518                let ty = &env.lookup_rust_ty(genv, place).with_span(stmt_span)?;
1519                let ctor = self
1520                    .default_refiner
1521                    .refine_ty_or_base(ty)
1522                    .with_span(stmt_span)?
1523                    .expect_base();
1524                raw_ptr_with_size(genv, kind, ctor)
1525            }
1526            Rvalue::Cast(kind, op, to) => {
1527                let from = self
1528                    .check_operand(infcx, env, stmt_span, op)
1529                    .with_span(stmt_span)?;
1530                self.check_cast(infcx, env, stmt_span, *kind, &from, to)
1531                    .with_span(stmt_span)
1532            }
1533            Rvalue::BinaryOp(bin_op, op1, op2) => {
1534                self.check_binary_op(infcx, env, stmt_span, *bin_op, op1, op2)
1535                    .with_span(stmt_span)
1536            }
1537
1538            Rvalue::UnaryOp(UnOp::PtrMetadata, Operand::Copy(place))
1539            | Rvalue::UnaryOp(UnOp::PtrMetadata, Operand::Move(place)) => {
1540                self.check_raw_ptr_metadata(infcx, env, stmt_span, place)
1541            }
1542            Rvalue::UnaryOp(un_op, op) => {
1543                self.check_unary_op(infcx, env, stmt_span, *un_op, op)
1544                    .with_span(stmt_span)
1545            }
1546            Rvalue::Discriminant(place) => {
1547                let ty = env
1548                    .lookup_place(&mut infcx.at(stmt_span), place)
1549                    .with_span(stmt_span)?;
1550                // HACK(nilehmann, mut-ref-unfolding) place should be unfolded here.
1551                let (adt_def, ..) = ty
1552                    .as_bty_skipping_existentials()
1553                    .unwrap_or_else(|| tracked_span_bug!())
1554                    .expect_adt();
1555                Ok(Ty::discr(adt_def.clone(), place.clone()))
1556            }
1557            Rvalue::Aggregate(
1558                AggregateKind::Adt(def_id, variant_idx, args, _, field_idx),
1559                operands,
1560            ) => {
1561                let actuals = self
1562                    .check_operands(infcx, env, stmt_span, operands)
1563                    .with_span(stmt_span)?;
1564                let sig = genv
1565                    .variant_sig(*def_id, *variant_idx)
1566                    .with_span(stmt_span)?
1567                    .ok_or_query_err(*def_id)
1568                    .with_span(stmt_span)?
1569                    .to_poly_fn_sig(*field_idx);
1570
1571                let args = instantiate_args_for_constructor(
1572                    genv,
1573                    self.checker_id.root_id().to_def_id(),
1574                    *def_id,
1575                    args,
1576                )
1577                .with_span(stmt_span)?;
1578                self.check_call(infcx, env, stmt_span, None, Some(*def_id), sig, &args, &actuals)
1579                    .map(|resolved_call| resolved_call.output)
1580            }
1581            Rvalue::Aggregate(AggregateKind::Array(arr_ty), operands) => {
1582                let args = self
1583                    .check_operands(infcx, env, stmt_span, operands)
1584                    .with_span(stmt_span)?;
1585                let arr_ty = self.refine_with_holes(arr_ty).with_span(stmt_span)?;
1586                self.check_mk_array(infcx, env, stmt_span, &args, arr_ty)
1587                    .with_span(stmt_span)
1588            }
1589            Rvalue::Aggregate(AggregateKind::Tuple, args) => {
1590                let tys = self
1591                    .check_operands(infcx, env, stmt_span, args)
1592                    .with_span(stmt_span)?;
1593                Ok(Ty::tuple(tys))
1594            }
1595            Rvalue::Aggregate(AggregateKind::Closure(did, args), operands) => {
1596                self.check_rvalue_closure(infcx, env, stmt_span, did, args, operands)
1597            }
1598            Rvalue::Aggregate(AggregateKind::Coroutine(did, args), ops) => {
1599                let coroutine_args = args.as_coroutine();
1600                let resume_ty = self
1601                    .refine_default(coroutine_args.resume_ty())
1602                    .with_span(stmt_span)?;
1603                let upvar_tys = self
1604                    .check_operands(infcx, env, stmt_span, ops)
1605                    .with_span(stmt_span)?;
1606                Ok(Ty::coroutine(*did, resume_ty, upvar_tys.into(), args.clone()))
1607            }
1608            Rvalue::ShallowInitBox(operand, _) => {
1609                self.check_operand(infcx, env, stmt_span, operand)
1610                    .with_span(stmt_span)?;
1611                Ty::mk_box_with_default_alloc(self.genv, Ty::uninit()).with_span(stmt_span)
1612            }
1613        }
1614    }
1615
1616    fn check_raw_ptr_metadata(
1617        &mut self,
1618        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1619        env: &mut TypeEnv,
1620        stmt_span: Span,
1621        place: &Place,
1622    ) -> Result<Ty> {
1623        let ty = env
1624            .lookup_place(&mut infcx.at(stmt_span), place)
1625            .with_span(stmt_span)?;
1626        let ty = match ty.kind() {
1627            TyKind::Indexed(BaseTy::RawPtrMetadata(ty), _)
1628            | TyKind::Indexed(BaseTy::Ref(_, ty, _), _) => ty,
1629            _ => tracked_span_bug!("check_metadata: bug! unexpected type `{ty:?}`"),
1630        };
1631        match ty.kind() {
1632            TyKind::Indexed(BaseTy::Array(_, len), _) => {
1633                let idx = Expr::from_const(self.genv.tcx(), len);
1634                Ok(Ty::indexed(BaseTy::Uint(UintTy::Usize), idx))
1635            }
1636            TyKind::Indexed(BaseTy::Slice(_), len) => {
1637                Ok(Ty::indexed(BaseTy::Uint(UintTy::Usize), len.clone()))
1638            }
1639            _ => Ok(Ty::unit()),
1640        }
1641    }
1642
1643    fn check_binary_op(
1644        &mut self,
1645        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1646        env: &mut TypeEnv,
1647        stmt_span: Span,
1648        bin_op: mir::BinOp,
1649        op1: &Operand<'tcx>,
1650        op2: &Operand<'tcx>,
1651    ) -> InferResult<Ty> {
1652        let ty1 = self.check_operand(infcx, env, stmt_span, op1)?;
1653        let ty2 = self.check_operand(infcx, env, stmt_span, op2)?;
1654
1655        match (ty1.kind(), ty2.kind()) {
1656            (TyKind::Indexed(bty1, idx1), TyKind::Indexed(bty2, idx2)) => {
1657                let rule =
1658                    primops::match_bin_op(bin_op, bty1, idx1, bty2, idx2, infcx.check_overflow);
1659                if let Some(pre) = rule.precondition {
1660                    infcx.at(stmt_span).check_pred(pre.pred, pre.reason);
1661                }
1662
1663                Ok(rule.output_type)
1664            }
1665            _ => tracked_span_bug!("incompatible types: `{ty1:?}` `{ty2:?}`"),
1666        }
1667    }
1668
1669    fn check_unary_op(
1670        &mut self,
1671        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1672        env: &mut TypeEnv,
1673        stmt_span: Span,
1674        un_op: mir::UnOp,
1675        op: &Operand<'tcx>,
1676    ) -> InferResult<Ty> {
1677        let ty = self.check_operand(infcx, env, stmt_span, op)?;
1678        match ty.kind() {
1679            TyKind::Indexed(bty, idx) => {
1680                let rule = primops::match_un_op(un_op, bty, idx, infcx.check_overflow);
1681                if let Some(pre) = rule.precondition {
1682                    infcx.at(stmt_span).check_pred(pre.pred, pre.reason);
1683                }
1684                Ok(rule.output_type)
1685            }
1686            _ => tracked_span_bug!("invalid type for unary operator `{un_op:?}` `{ty:?}`"),
1687        }
1688    }
1689
1690    fn check_mk_array(
1691        &mut self,
1692        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1693        env: &mut TypeEnv,
1694        stmt_span: Span,
1695        args: &[Ty],
1696        arr_ty: Ty,
1697    ) -> InferResult<Ty> {
1698        let arr_ty = infcx.ensure_resolved_evars(|infcx| {
1699            let arr_ty =
1700                arr_ty.replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind));
1701
1702            let (arr_ty, pred) = arr_ty.unconstr();
1703            let mut at = infcx.at(stmt_span);
1704            at.check_pred(&pred, ConstrReason::Other);
1705            for ty in args {
1706                at.subtyping_with_env(env, ty, &arr_ty, ConstrReason::Other)?;
1707            }
1708            Ok(arr_ty)
1709        })?;
1710        let arr_ty = infcx.fully_resolve_evars(&arr_ty);
1711
1712        Ok(Ty::array(arr_ty, rty::Const::from_usize(self.genv.tcx(), args.len())))
1713    }
1714
1715    fn check_cast(
1716        &self,
1717        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1718        env: &mut TypeEnv,
1719        stmt_span: Span,
1720        kind: CastKind,
1721        from: &Ty,
1722        to: &ty::Ty,
1723    ) -> InferResult<Ty> {
1724        use ty::TyKind as RustTy;
1725        let ty = match kind {
1726            CastKind::PointerExposeProvenance => {
1727                match to.kind() {
1728                    RustTy::Int(int_ty) => Ty::int(*int_ty),
1729                    RustTy::Uint(uint_ty) => Ty::uint(*uint_ty),
1730                    _ => tracked_span_bug!("unsupported PointerExposeProvenance cast"),
1731                }
1732            }
1733            CastKind::IntToInt => {
1734                match (from.kind(), to.kind()) {
1735                    (Bool!(idx), RustTy::Int(int_ty)) => bool_int_cast(idx, *int_ty),
1736                    (Bool!(idx), RustTy::Uint(uint_ty)) => bool_uint_cast(idx, *uint_ty),
1737                    (Int!(int_ty1, idx), RustTy::Int(int_ty2)) => {
1738                        int_int_cast(idx, *int_ty1, *int_ty2)
1739                    }
1740                    (Uint!(uint_ty1, idx), RustTy::Uint(uint_ty2)) => {
1741                        uint_uint_cast(idx, *uint_ty1, *uint_ty2)
1742                    }
1743                    (Uint!(uint_ty, idx), RustTy::Int(int_ty)) => {
1744                        uint_int_cast(idx, *uint_ty, *int_ty)
1745                    }
1746                    (Int!(int_ty, idx), RustTy::Uint(uint_ty)) => {
1747                        int_uint_cast(idx, *int_ty, *uint_ty)
1748                    }
1749                    (TyKind::Discr(adt_def, _), RustTy::Int(int_ty)) => {
1750                        Self::discr_to_int_cast(adt_def, BaseTy::Int(*int_ty))
1751                    }
1752                    (TyKind::Discr(adt_def, _place), RustTy::Uint(uint_ty)) => {
1753                        Self::discr_to_int_cast(adt_def, BaseTy::Uint(*uint_ty))
1754                    }
1755                    (Char!(idx), RustTy::Uint(uint_ty)) => char_uint_cast(idx, *uint_ty),
1756                    (Uint!(_, idx), RustTy::Char) => uint_char_cast(idx),
1757                    _ => {
1758                        tracked_span_bug!("invalid int to int cast {from:?} --> {to:?}")
1759                    }
1760                }
1761            }
1762            CastKind::PointerCoercion(mir::PointerCast::Unsize) => {
1763                self.check_unsize_cast(infcx, env, stmt_span, from, to)?
1764            }
1765            CastKind::PointerCoercion(mir::PointerCast::MutToConstPointer) => {
1766                match from.kind() {
1767                    TyKind::Indexed(BaseTy::RawPtr(inner_ty, Mutability::Mut), idx) => {
1768                        Ty::indexed(BaseTy::RawPtr(inner_ty.clone(), Mutability::Not), idx.clone())
1769                    }
1770                    _ => self.refine_default(to)?,
1771                }
1772            }
1773            CastKind::FloatToInt
1774            | CastKind::IntToFloat
1775            | CastKind::FloatToFloat
1776            | CastKind::PtrToPtr
1777            | CastKind::PointerCoercion(mir::PointerCast::ClosureFnPointer)
1778            | CastKind::PointerWithExposedProvenance => self.refine_default(to)?,
1779            CastKind::PointerCoercion(mir::PointerCast::ReifyFnPointer(_)) => {
1780                let to = self.refine_default(to)?;
1781                if let TyKind::Indexed(BaseTy::FnDef(def_id, args), _) = from.kind()
1782                    && let TyKind::Indexed(BaseTy::FnPtr(super_sig), _) = to.kind()
1783                {
1784                    let current_did = infcx.def_id;
1785                    let sub_sig =
1786                        SubFn::Poly(current_did, infcx.genv.fn_sig(*def_id)?, args.clone());
1787                    // TODO:CLOSURE:2 TODO(RJ) dicey maneuver? assumes that sig_b is unrefined?
1788                    check_fn_subtyping(infcx, sub_sig, super_sig, stmt_span)?;
1789                    to
1790                } else {
1791                    tracked_span_bug!("invalid cast from `{from:?}` to `{to:?}`")
1792                }
1793            }
1794        };
1795        Ok(ty)
1796    }
1797
1798    fn discr_to_int_cast(adt_def: &AdtDef, bty: BaseTy) -> Ty {
1799        // TODO: This could be a giant disjunction, maybe better (if less precise) to use the interval?
1800        let vals = adt_def
1801            .discriminants()
1802            .map(|(_, idx)| Expr::eq(Expr::nu(), Expr::from_bits(&bty, idx)))
1803            .collect_vec();
1804        Ty::exists_with_constr(bty, Expr::or_from_iter(vals))
1805    }
1806
1807    fn check_unsize_cast(
1808        &self,
1809        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1810        env: &mut TypeEnv,
1811        span: Span,
1812        src: &Ty,
1813        dst: &ty::Ty,
1814    ) -> InferResult<Ty> {
1815        // Convert `ptr` to `&mut`
1816        let src = if let TyKind::Ptr(PtrKind::Mut(re), path) = src.kind() {
1817            env.ptr_to_ref(
1818                &mut infcx.at(span),
1819                ConstrReason::Other,
1820                *re,
1821                path,
1822                PtrToRefBound::Identity,
1823            )?
1824        } else {
1825            src.clone()
1826        };
1827
1828        if let ty::TyKind::Ref(_, deref_ty, _) = dst.kind()
1829            && let ty::TyKind::Dynamic(..) = deref_ty.kind()
1830        {
1831            return Ok(self.refine_default(dst)?);
1832        }
1833
1834        // `&mut [T; n] -> &mut [T]` or `&[T; n] -> &[T]`
1835        if let TyKind::Indexed(BaseTy::Ref(_, deref_ty, _), _) = src.kind()
1836            && let TyKind::Indexed(BaseTy::Array(arr_ty, arr_len), _) = deref_ty.kind()
1837            && let ty::TyKind::Ref(re, _, mutbl) = dst.kind()
1838        {
1839            let idx = Expr::from_const(self.genv.tcx(), arr_len);
1840            Ok(Ty::mk_ref(*re, Ty::indexed(BaseTy::Slice(arr_ty.clone()), idx), *mutbl))
1841
1842        // `Box<[T; n]> -> Box<[T]>`
1843        } else if let TyKind::Indexed(BaseTy::Adt(adt_def, args), _) = src.kind()
1844            && adt_def.is_box()
1845            && let (deref_ty, alloc_ty) = args.box_args()
1846            && let TyKind::Indexed(BaseTy::Array(arr_ty, arr_len), _) = deref_ty.kind()
1847        {
1848            let idx = Expr::from_const(self.genv.tcx(), arr_len);
1849            Ok(Ty::mk_box(
1850                self.genv,
1851                Ty::indexed(BaseTy::Slice(arr_ty.clone()), idx),
1852                alloc_ty.clone(),
1853            )?)
1854        } else {
1855            Err(query_bug!("unsupported unsize cast from `{src:?}` to `{dst:?}`"))?
1856        }
1857    }
1858
1859    fn check_operands(
1860        &mut self,
1861        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1862        env: &mut TypeEnv,
1863        span: Span,
1864        operands: &[Operand<'tcx>],
1865    ) -> InferResult<Vec<Ty>> {
1866        operands
1867            .iter()
1868            .map(|op| self.check_operand(infcx, env, span, op))
1869            .try_collect()
1870    }
1871
1872    fn check_operand(
1873        &mut self,
1874        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1875        env: &mut TypeEnv,
1876        span: Span,
1877        operand: &Operand<'tcx>,
1878    ) -> InferResult<Ty> {
1879        let ty = match operand {
1880            Operand::Copy(p) => env.lookup_place(&mut infcx.at(span), p)?,
1881            Operand::Move(p) => env.move_place(&mut infcx.at(span), p)?,
1882            Operand::Constant(c) => self.check_constant(infcx, c)?,
1883        };
1884        Ok(infcx.hoister(true).hoist(&ty))
1885    }
1886
1887    fn check_constant(
1888        &mut self,
1889        infcx: &InferCtxt<'_, 'genv, 'tcx>,
1890        constant: &ConstOperand<'tcx>,
1891    ) -> QueryResult<Ty> {
1892        use rustc_middle::mir::Const;
1893        match constant.const_ {
1894            Const::Ty(ty, cst) => self.check_ty_const(constant, cst, ty)?,
1895            Const::Val(val, ty) => self.check_const_val(val, ty)?,
1896            Const::Unevaluated(uneval, ty) => {
1897                self.check_uneval_const(infcx, constant, uneval, ty)?
1898            }
1899        }
1900        .map_or_else(|| self.refine_default(&constant.ty), Ok)
1901    }
1902
1903    fn check_ty_const(
1904        &mut self,
1905        constant: &ConstOperand<'tcx>,
1906        cst: rustc_middle::ty::Const<'tcx>,
1907        ty: rustc_middle::ty::Ty<'tcx>,
1908    ) -> QueryResult<Option<Ty>> {
1909        use rustc_middle::ty::ConstKind;
1910        match cst.kind() {
1911            ConstKind::Param(param) => {
1912                let idx = Expr::const_generic(param);
1913                let ctor = self
1914                    .default_refiner
1915                    .refine_ty_or_base(&constant.ty)?
1916                    .expect_base();
1917                Ok(Some(ctor.replace_bound_reft(&idx).to_ty()))
1918            }
1919            ConstKind::Value(val_tree) => {
1920                let val = self.genv.tcx().valtree_to_const_val(val_tree);
1921                Ok(self.check_const_val(val, ty)?)
1922            }
1923            _ => Ok(None),
1924        }
1925    }
1926
1927    fn check_const_val(
1928        &mut self,
1929        val: rustc_middle::mir::ConstValue,
1930        ty: rustc_middle::ty::Ty<'tcx>,
1931    ) -> QueryResult<Option<Ty>> {
1932        use rustc_middle::{mir::ConstValue, ty};
1933        match val {
1934            ConstValue::Scalar(scalar) => self.check_scalar(scalar, ty),
1935            ConstValue::ZeroSized if ty.is_unit() => Ok(Some(Ty::unit())),
1936            ConstValue::Slice { .. } => {
1937                if let ty::Ref(_, ref_ty, Mutability::Not) = ty.kind()
1938                    && ref_ty.is_str()
1939                    && let Some(data) = val.try_get_slice_bytes_for_diagnostics(self.genv.tcx())
1940                {
1941                    let str = String::from_utf8_lossy(data);
1942                    let idx = Expr::constant(Constant::Str(Symbol::intern(&str)));
1943                    Ok(Some(Ty::mk_ref(ReErased, Ty::indexed(BaseTy::Str, idx), Mutability::Not)))
1944                } else {
1945                    Ok(None)
1946                }
1947            }
1948            _ => Ok(None),
1949        }
1950    }
1951
1952    fn check_uneval_const(
1953        &mut self,
1954        infcx: &InferCtxt<'_, 'genv, 'tcx>,
1955        constant: &ConstOperand<'tcx>,
1956        uneval: rustc_middle::mir::UnevaluatedConst<'tcx>,
1957        ty: rustc_middle::ty::Ty<'tcx>,
1958    ) -> QueryResult<Option<Ty>> {
1959        // 1. Use template for promoted constants, if applicable
1960        if let Some(promoted) = uneval.promoted
1961            && let Some(ty) = self.promoted.get(promoted)
1962        {
1963            return Ok(Some(ty.clone()));
1964        }
1965
1966        // 2. `Genv::constant_info` cannot handle constants with generics, so, we evaluate
1967        //    them here. These mostly come from inline consts, e.g., `const { 1 + 1 }`, because
1968        //    the generic_const_items feature is unstable.
1969        if !uneval.args.is_empty() {
1970            let tcx = self.genv.tcx();
1971            let param_env = tcx.param_env(self.checker_id.root_id());
1972            let typing_env = infcx.region_infcx.typing_env(param_env);
1973            if let Ok(val) = tcx.const_eval_resolve(typing_env, uneval, constant.span) {
1974                return self.check_const_val(val, ty);
1975            } else {
1976                return Ok(None);
1977            }
1978        }
1979
1980        // 3. Try to see if we have `consant_info` for it.
1981        if let rty::TyOrBase::Base(ctor) = self.default_refiner.refine_ty_or_base(&constant.ty)?
1982            && let rty::ConstantInfo::Interpreted(idx, _) = self.genv.constant_info(uneval.def)?
1983        {
1984            return Ok(Some(ctor.replace_bound_reft(&idx).to_ty()));
1985        }
1986
1987        Ok(None)
1988    }
1989
1990    fn check_scalar(
1991        &mut self,
1992        scalar: rustc_middle::mir::interpret::Scalar,
1993        ty: rustc_middle::ty::Ty<'tcx>,
1994    ) -> QueryResult<Option<Ty>> {
1995        use rustc_middle::mir::interpret::{GlobalAlloc, Scalar};
1996        match scalar {
1997            Scalar::Int(scalar_int) => Ok(self.check_scalar_int(scalar_int, ty)),
1998            Scalar::Ptr(ptr, _) => {
1999                let alloc_id = ptr.provenance.alloc_id();
2000                if let GlobalAlloc::Static(def_id) = self.genv.tcx().global_alloc(alloc_id)
2001                    && let rty::StaticInfo::Known(ty) = self.genv.static_info(def_id)?
2002                    && !self.genv.tcx().is_mutable_static(def_id)
2003                // TODO: mutable statics!
2004                {
2005                    Ok(Some(Ty::mk_ref(ReErased, ty, Mutability::Not)))
2006                } else {
2007                    Ok(None)
2008                }
2009            }
2010        }
2011    }
2012
2013    fn check_scalar_int(
2014        &mut self,
2015        scalar: rustc_middle::ty::ScalarInt,
2016        ty: rustc_middle::ty::Ty<'tcx>,
2017    ) -> Option<Ty> {
2018        use flux_rustc_bridge::const_eval::{scalar_to_int, scalar_to_uint};
2019        use rustc_middle::ty;
2020
2021        let tcx = self.genv.tcx();
2022
2023        match ty.kind() {
2024            ty::Int(int_ty) => {
2025                let idx = Expr::constant(Constant::from(scalar_to_int(tcx, scalar, *int_ty)));
2026                Some(Ty::indexed(BaseTy::Int(*int_ty), idx))
2027            }
2028            ty::Uint(uint_ty) => {
2029                let idx = Expr::constant(Constant::from(scalar_to_uint(tcx, scalar, *uint_ty)));
2030                Some(Ty::indexed(BaseTy::Uint(*uint_ty), idx))
2031            }
2032            ty::Float(float_ty) => Some(Ty::float(*float_ty)),
2033            ty::Char => {
2034                let idx = Expr::constant(Constant::Char(scalar.try_into().unwrap()));
2035                Some(Ty::indexed(BaseTy::Char, idx))
2036            }
2037            ty::Bool => {
2038                let idx = Expr::constant(Constant::Bool(scalar.try_to_bool().unwrap()));
2039                Some(Ty::indexed(BaseTy::Bool, idx))
2040            }
2041            // ty::Tuple(tys) if tys.is_empty() => Constant::Unit,
2042            _ => None,
2043        }
2044    }
2045
2046    fn check_ghost_statements_at(
2047        &mut self,
2048        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2049        env: &mut TypeEnv,
2050        point: Point,
2051        span: Span,
2052    ) -> Result {
2053        bug::track_span(span, || {
2054            for stmt in self.ghost_stmts().statements_at(point) {
2055                self.check_ghost_statement(infcx, env, stmt, span)
2056                    .with_span(span)?;
2057            }
2058            Ok(())
2059        })
2060    }
2061
2062    fn check_ghost_statement(
2063        &mut self,
2064        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2065        env: &mut TypeEnv,
2066        stmt: &GhostStatement,
2067        span: Span,
2068    ) -> InferResult {
2069        dbg::statement!("start", stmt, infcx, env, span, &self);
2070        match stmt {
2071            GhostStatement::Fold(place) => {
2072                env.fold(&mut infcx.at(span), place)?;
2073            }
2074            GhostStatement::Unfold(place) => {
2075                env.unfold(infcx, place, span)?;
2076            }
2077            GhostStatement::Unblock(place) => env.unblock(infcx, place),
2078            GhostStatement::PtrToRef(place) => {
2079                env.ptr_to_ref_at_place(&mut infcx.at(span), place)?;
2080            }
2081        }
2082        dbg::statement!("end", stmt, infcx, env, span, &self);
2083        Ok(())
2084    }
2085
2086    #[track_caller]
2087    fn marker_at_dominator(&self, bb: BasicBlock) -> &Marker {
2088        marker_at_dominator(self.body, &self.markers, bb)
2089    }
2090
2091    fn dominators(&self) -> &'ck Dominators<BasicBlock> {
2092        self.body.dominators()
2093    }
2094
2095    fn ghost_stmts(&self) -> &'ck GhostStatements {
2096        &self.inherited.ghost_stmts[&self.checker_id]
2097    }
2098
2099    fn refine_default<T: Refine>(&self, ty: &T) -> QueryResult<T::Output> {
2100        ty.refine(&self.default_refiner)
2101    }
2102
2103    fn refine_with_holes<T: Refine>(&self, ty: &T) -> QueryResult<<T as Refine>::Output> {
2104        ty.refine(&Refiner::with_holes(self.genv, self.checker_id.root_id().to_def_id())?)
2105    }
2106}
2107
2108/// Converts a reference into a raw-ptr, tracking size etc.
2109///
2110///     &mut T => *mut{p: p.size == T::size_of() && p.base == p.addr && p.addr % T::align_of() == 0 } T
2111///
2112/// see test `fn ref_to_ptr_read` in `crates/flux/tests/tests/with_deps/pos/extern_specs/flux_core_ptr01.rs`
2113fn raw_ptr_with_size(genv: GlobalEnv, kind: &RawPtrKind, ctor: SubsetTyCtor) -> Result<Ty> {
2114    let sized_id = genv.tcx().require_lang_item(LangItem::Sized, DUMMY_SP);
2115    let bty = BaseTy::RawPtr(ctor.to_ty(), kind.to_mutbl_lossy());
2116    let args = rty::List::from_arr([GenericArg::Base(ctor)]);
2117    let size_of_expr = Expr::alias(
2118        AliasReft {
2119            assoc_id: genv.require_builtin_assoc_reft(sized_id, sym::size_of),
2120            args: args.clone(),
2121        },
2122        rty::List::empty(),
2123    );
2124    let align_of_expr = Expr::alias(
2125        AliasReft { assoc_id: genv.require_builtin_assoc_reft(sized_id, sym::align_of), args },
2126        rty::List::empty(),
2127    );
2128
2129    let nu = Expr::nu();
2130    let base = Expr::field_proj(&nu, rty::FieldProj::RawPtr { field: rty::RawPtrField::Base });
2131    let addr = Expr::field_proj(&nu, rty::FieldProj::RawPtr { field: rty::RawPtrField::Addr });
2132    let size = Expr::field_proj(nu, rty::FieldProj::RawPtr { field: rty::RawPtrField::Size });
2133
2134    let pred = Expr::and_from_iter([
2135        Expr::eq(base, addr.clone()),
2136        Expr::ne(addr.clone(), Expr::zero()),
2137        Expr::eq(size, size_of_expr),
2138        Expr::eq(Expr::binary_op(BinOp::Mod(Sort::Int), addr, align_of_expr), Expr::zero()),
2139    ]);
2140
2141    let ty = Ty::exists_with_constr(bty, pred);
2142    Ok(ty)
2143}
2144
2145fn instantiate_args_for_fun_call(
2146    genv: GlobalEnv,
2147    caller_id: DefId,
2148    callee_id: DefId,
2149    args: &ty::GenericArgs,
2150) -> QueryResult<Vec<rty::GenericArg>> {
2151    let params_in_clauses = collect_params_in_clauses(genv, callee_id);
2152    let assumed_parametric_params = genv.assume_parametric_params(callee_id);
2153
2154    let hole_refiner = Refiner::new_for_item(genv, caller_id, |bty| {
2155        let sort = bty.sort();
2156        let bty = bty.shift_in_escaping(1);
2157        let constr = if !sort.is_unit() {
2158            rty::SubsetTy::new(bty, Expr::nu(), Expr::hole(rty::HoleKind::Pred))
2159        } else {
2160            rty::SubsetTy::trivial(bty, Expr::nu())
2161        };
2162        Binder::bind_with_sort(constr, sort)
2163    })?;
2164    let default_refiner = Refiner::default_for_item(genv, caller_id)?;
2165
2166    let callee_generics = genv.generics_of(callee_id)?;
2167    args.iter()
2168        .enumerate()
2169        .map(|(idx, arg)| {
2170            let param = callee_generics.param_at(idx, genv)?;
2171            let is_parametric = !params_in_clauses.contains(&idx)
2172                || assumed_parametric_params.contains(&(idx as u32));
2173            let refiner = if is_parametric { &hole_refiner } else { &default_refiner };
2174            refiner.refine_generic_arg(&param, arg)
2175        })
2176        .collect()
2177}
2178
2179fn instantiate_args_for_constructor(
2180    genv: GlobalEnv,
2181    caller_id: DefId,
2182    adt_id: DefId,
2183    args: &ty::GenericArgs,
2184) -> QueryResult<Vec<rty::GenericArg>> {
2185    let params_in_clauses = collect_params_in_clauses(genv, adt_id);
2186
2187    let adt_generics = genv.generics_of(adt_id)?;
2188    let hole_refiner = Refiner::with_holes(genv, caller_id)?;
2189    let default_refiner = Refiner::default_for_item(genv, caller_id)?;
2190    args.iter()
2191        .enumerate()
2192        .map(|(idx, arg)| {
2193            let param = adt_generics.param_at(idx, genv)?;
2194            let refiner =
2195                if params_in_clauses.contains(&idx) { &default_refiner } else { &hole_refiner };
2196            refiner.refine_generic_arg(&param, arg)
2197        })
2198        .collect()
2199}
2200
2201fn collect_params_in_clauses(genv: GlobalEnv, def_id: DefId) -> UnordSet<usize> {
2202    let tcx = genv.tcx();
2203    struct Collector {
2204        params: UnordSet<usize>,
2205    }
2206
2207    impl rustc_middle::ty::TypeVisitor<TyCtxt<'_>> for Collector {
2208        fn visit_ty(&mut self, t: rustc_middle::ty::Ty) {
2209            if let rustc_middle::ty::Param(param_ty) = t.kind() {
2210                self.params.insert(param_ty.index as usize);
2211            }
2212            t.super_visit_with(self);
2213        }
2214    }
2215    let mut vis = Collector { params: UnordSet::new() };
2216
2217    let span = genv.tcx().def_span(def_id);
2218    for (clause, _) in all_predicates_of(tcx, def_id) {
2219        if let Some(trait_pred) = clause.as_trait_clause() {
2220            let trait_id = trait_pred.def_id();
2221            let ignore = [
2222                LangItem::MetaSized,
2223                LangItem::Sized,
2224                LangItem::Tuple,
2225                LangItem::Copy,
2226                LangItem::Destruct,
2227            ];
2228            if ignore
2229                .iter()
2230                .any(|lang_item| tcx.require_lang_item(*lang_item, span) == trait_id)
2231            {
2232                continue;
2233            }
2234
2235            if tcx.fn_trait_kind_from_def_id(trait_id).is_some() {
2236                continue;
2237            }
2238            if tcx.get_diagnostic_item(sym::Hash) == Some(trait_id) {
2239                continue;
2240            }
2241            if tcx.get_diagnostic_item(sym::Eq) == Some(trait_id) {
2242                continue;
2243            }
2244        }
2245        if let Some(proj_pred) = clause.as_projection_clause() {
2246            let assoc_id = proj_pred.item_def_id();
2247            if genv.is_fn_output(assoc_id) {
2248                continue;
2249            }
2250        }
2251        if let Some(outlives_pred) = clause.as_type_outlives_clause() {
2252            // We skip outlives bounds if they are not 'static. A 'static bound means the type
2253            // implements `Any` which makes it unsound to instantiate the argument with refinements.
2254            if outlives_pred.skip_binder().1 != tcx.lifetimes.re_static {
2255                continue;
2256            }
2257        }
2258        clause.visit_with(&mut vis);
2259    }
2260    vis.params
2261}
2262
2263fn all_predicates_of(
2264    tcx: TyCtxt<'_>,
2265    id: DefId,
2266) -> impl Iterator<Item = &(rustc_middle::ty::Clause<'_>, Span)> {
2267    let mut next_id = Some(id);
2268    iter::from_fn(move || {
2269        next_id.take().map(|id| {
2270            let preds = tcx.predicates_of(id);
2271            next_id = preds.parent;
2272            preds.predicates.iter()
2273        })
2274    })
2275    .flatten()
2276}
2277
2278struct SkipConstr;
2279
2280impl TypeFolder for SkipConstr {
2281    fn fold_ty(&mut self, ty: &rty::Ty) -> rty::Ty {
2282        if let rty::TyKind::Constr(_, inner_ty) = ty.kind() {
2283            inner_ty.fold_with(self)
2284        } else {
2285            ty.super_fold_with(self)
2286        }
2287    }
2288}
2289
2290fn is_indexed_mut_skipping_constr(ty: &Ty) -> bool {
2291    let ty = SkipConstr.fold_ty(ty);
2292    if let rty::Ref!(_, inner_ty, Mutability::Mut) = ty.kind()
2293        && let TyKind::Indexed(..) = inner_ty.kind()
2294    {
2295        true
2296    } else {
2297        false
2298    }
2299}
2300
2301/// HACK(nilehmann) This let us infer parameters under mutable references for the simple case
2302/// where the formal argument is of the form `&mut B[@n]`, e.g., the type of the first argument
2303/// to `RVec::get_mut` is `&mut RVec<T>[@n]`. We should remove this after we implement opening of
2304/// mutable references.
2305fn infer_under_mut_ref_hack(rcx: &mut InferCtxt, actuals: &[Ty], fn_sig: &PolyFnSig) -> Vec<Ty> {
2306    iter::zip(actuals, fn_sig.skip_binder_ref().inputs())
2307        .map(|(actual, formal)| {
2308            if let rty::Ref!(re, deref_ty, Mutability::Mut) = actual.kind()
2309                && is_indexed_mut_skipping_constr(formal)
2310            {
2311                rty::Ty::mk_ref(*re, rcx.unpack(deref_ty), Mutability::Mut)
2312            } else {
2313                actual.clone()
2314            }
2315        })
2316        .collect()
2317}
2318
2319impl Mode for ShapeMode {
2320    const NAME: &str = "shape";
2321
2322    fn enter_basic_block<'ck, 'genv, 'tcx>(
2323        ck: &mut Checker<'ck, 'genv, 'tcx, ShapeMode>,
2324        _infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2325        bb: BasicBlock,
2326    ) -> TypeEnv<'ck> {
2327        ck.inherited.mode.bb_envs[&ck.checker_id][&bb].enter(&ck.body.local_decls)
2328    }
2329
2330    fn check_goto_join_point<'genv, 'tcx>(
2331        ck: &mut Checker<'_, 'genv, 'tcx, ShapeMode>,
2332        _: InferCtxt<'_, 'genv, 'tcx>,
2333        env: TypeEnv,
2334        span: Span,
2335        target: BasicBlock,
2336    ) -> Result<bool> {
2337        let bb_envs = &mut ck.inherited.mode.bb_envs;
2338        let target_bb_env = bb_envs.entry(ck.checker_id).or_default().get(&target);
2339        dbg::shape_goto_enter!(target, env, target_bb_env);
2340
2341        let modified = match bb_envs.entry(ck.checker_id).or_default().entry(target) {
2342            Entry::Occupied(mut entry) => entry.get_mut().join(env, span),
2343            Entry::Vacant(entry) => {
2344                let scope = marker_at_dominator(ck.body, &ck.markers, target)
2345                    .scope()
2346                    .unwrap_or_else(|| tracked_span_bug!());
2347                entry.insert(env.into_infer(scope));
2348                true
2349            }
2350        };
2351
2352        dbg::shape_goto_exit!(target, bb_envs[&ck.checker_id].get(&target));
2353        Ok(modified)
2354    }
2355
2356    fn clear(ck: &mut Checker<ShapeMode>, root: BasicBlock) {
2357        ck.visited.remove(root);
2358        for bb in ck.body.basic_blocks.indices() {
2359            if bb != root && ck.dominators().dominates(root, bb) {
2360                ck.inherited
2361                    .mode
2362                    .bb_envs
2363                    .entry(ck.checker_id)
2364                    .or_default()
2365                    .remove(&bb);
2366                ck.visited.remove(bb);
2367            }
2368        }
2369    }
2370}
2371
2372impl Mode for RefineMode {
2373    const NAME: &str = "refine";
2374
2375    fn enter_basic_block<'ck, 'genv, 'tcx>(
2376        ck: &mut Checker<'ck, 'genv, 'tcx, RefineMode>,
2377        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2378        bb: BasicBlock,
2379    ) -> TypeEnv<'ck> {
2380        ck.inherited.mode.bb_envs[&ck.checker_id][&bb].enter(infcx, &ck.body.local_decls)
2381    }
2382
2383    fn check_goto_join_point(
2384        ck: &mut Checker<RefineMode>,
2385        mut infcx: InferCtxt,
2386        env: TypeEnv,
2387        terminator_span: Span,
2388        target: BasicBlock,
2389    ) -> Result<bool> {
2390        let bb_env = &ck.inherited.mode.bb_envs[&ck.checker_id][&target];
2391        tracked_span_dbg_assert_eq!(
2392            &ck.marker_at_dominator(target)
2393                .scope()
2394                .unwrap_or_else(|| tracked_span_bug!()),
2395            bb_env.scope()
2396        );
2397
2398        dbg::refine_goto!(target, infcx, env, bb_env);
2399
2400        env.check_goto(&mut infcx.at(terminator_span), bb_env, target)
2401            .with_span(terminator_span)?;
2402
2403        Ok(!ck.visited.contains(target))
2404    }
2405
2406    fn clear(_ck: &mut Checker<RefineMode>, _bb: BasicBlock) {
2407        bug!();
2408    }
2409}
2410
2411fn bool_int_cast(b: &Expr, int_ty: IntTy) -> Ty {
2412    let idx = Expr::ite(b, 1, 0);
2413    Ty::indexed(BaseTy::Int(int_ty), idx)
2414}
2415
2416/// Unlike [`char_uint_cast`] rust only allows `u8` to `char` casts, which are
2417/// non-lossy, so we can use indexed type directly.
2418fn uint_char_cast(idx: &Expr) -> Ty {
2419    let idx = Expr::cast(rty::Sort::Int, rty::Sort::Char, idx.clone());
2420    Ty::indexed(BaseTy::Char, idx)
2421}
2422
2423fn char_uint_cast(idx: &Expr, uint_ty: UintTy) -> Ty {
2424    let idx = Expr::cast(rty::Sort::Char, rty::Sort::Int, idx.clone());
2425    if uint_bit_width(uint_ty) >= 32 {
2426        // non-lossy cast: uint[cast(idx)]
2427        Ty::indexed(BaseTy::Uint(uint_ty), idx)
2428    } else {
2429        // lossy-cast: uint{v: cast(idx) <= max_value => v == cast(idx) }
2430        guarded_uint_ty(&idx, uint_ty)
2431    }
2432}
2433
2434fn bool_uint_cast(b: &Expr, uint_ty: UintTy) -> Ty {
2435    let idx = Expr::ite(b, 1, 0);
2436    Ty::indexed(BaseTy::Uint(uint_ty), idx)
2437}
2438
2439fn int_int_cast(idx: &Expr, int_ty1: IntTy, int_ty2: IntTy) -> Ty {
2440    if int_bit_width(int_ty1) <= int_bit_width(int_ty2) {
2441        Ty::indexed(BaseTy::Int(int_ty2), idx.clone())
2442    } else {
2443        Ty::int(int_ty2)
2444    }
2445}
2446
2447fn uint_int_cast(idx: &Expr, uint_ty: UintTy, int_ty: IntTy) -> Ty {
2448    if uint_bit_width(uint_ty) < int_bit_width(int_ty) {
2449        Ty::indexed(BaseTy::Int(int_ty), idx.clone())
2450    } else {
2451        Ty::int(int_ty)
2452    }
2453}
2454
2455fn int_uint_cast(idx: &Expr, int_ty: IntTy, uint_ty: UintTy) -> Ty {
2456    let non_neg = Expr::ge(idx.clone(), Expr::zero());
2457
2458    let guard: Expr = if int_bit_width(int_ty) <= uint_bit_width(uint_ty) {
2459        non_neg
2460    } else {
2461        // Cast is still possible if the value is known to fit.
2462        let fits = Expr::le(idx.clone(), Expr::uint_max(uint_ty));
2463        Expr::and(non_neg, fits)
2464    };
2465
2466    let eq = Expr::eq(Expr::nu(), idx.clone());
2467    Ty::exists_with_constr(BaseTy::Uint(uint_ty), Expr::implies(guard, eq))
2468}
2469
2470fn guarded_uint_ty(idx: &Expr, uint_ty: UintTy) -> Ty {
2471    // uint_ty2{v: idx <= max_value => v == idx }
2472    let max_value = Expr::uint_max(uint_ty);
2473    let guard = Expr::le(idx.clone(), max_value);
2474    let eq = Expr::eq(Expr::nu(), idx.clone());
2475    Ty::exists_with_constr(BaseTy::Uint(uint_ty), Expr::implies(guard, eq))
2476}
2477
2478fn uint_uint_cast(idx: &Expr, uint_ty1: UintTy, uint_ty2: UintTy) -> Ty {
2479    if uint_bit_width(uint_ty1) <= uint_bit_width(uint_ty2) {
2480        Ty::indexed(BaseTy::Uint(uint_ty2), idx.clone())
2481    } else {
2482        guarded_uint_ty(idx, uint_ty2)
2483    }
2484}
2485
2486fn uint_bit_width(uint_ty: UintTy) -> u64 {
2487    uint_ty
2488        .bit_width()
2489        .unwrap_or(config::pointer_width().bits())
2490}
2491
2492fn int_bit_width(int_ty: IntTy) -> u64 {
2493    int_ty.bit_width().unwrap_or(config::pointer_width().bits())
2494}
2495
2496impl ShapeResult {
2497    fn into_bb_envs(
2498        self,
2499        infcx: &mut InferCtxtRoot,
2500        body: &Body,
2501    ) -> FxHashMap<CheckerId, FxHashMap<BasicBlock, BasicBlockEnv>> {
2502        self.0
2503            .into_iter()
2504            .map(|(checker_id, shapes)| {
2505                let bb_envs = shapes
2506                    .into_iter()
2507                    .map(|(bb, shape)| (bb, shape.into_bb_env(infcx, body)))
2508                    .collect();
2509                (checker_id, bb_envs)
2510            })
2511            .collect()
2512    }
2513}
2514
2515fn marker_at_dominator<'a>(
2516    body: &Body,
2517    markers: &'a IndexVec<BasicBlock, Option<Marker>>,
2518    bb: BasicBlock,
2519) -> &'a Marker {
2520    let dominator = body
2521        .dominators()
2522        .immediate_dominator(bb)
2523        .unwrap_or_else(|| tracked_span_bug!());
2524    markers[dominator]
2525        .as_ref()
2526        .unwrap_or_else(|| tracked_span_bug!())
2527}
2528
2529pub(crate) mod errors {
2530    use flux_errors::{E0999, ErrorGuaranteed};
2531    use flux_infer::infer::InferErr;
2532    use flux_middle::{global_env::GlobalEnv, queries::ErrCtxt};
2533    use rustc_errors::Diagnostic;
2534    use rustc_hir::def_id::LocalDefId;
2535    use rustc_span::Span;
2536
2537    use crate::fluent_generated as fluent;
2538
2539    #[derive(Debug)]
2540    pub struct CheckerError {
2541        kind: InferErr,
2542        span: Span,
2543    }
2544
2545    impl CheckerError {
2546        pub fn emit(self, genv: GlobalEnv, fn_def_id: LocalDefId) -> ErrorGuaranteed {
2547            let dcx = genv.sess().dcx().handle();
2548            match self.kind {
2549                InferErr::UnsolvedEvar(_) => {
2550                    let mut diag =
2551                        dcx.struct_span_err(self.span, fluent::refineck_param_inference_error);
2552                    diag.code(E0999);
2553                    diag.emit()
2554                }
2555                InferErr::Query(err) => {
2556                    let level = rustc_errors::Level::Error;
2557                    err.at(ErrCtxt::FnCheck(self.span, fn_def_id))
2558                        .into_diag(dcx, level)
2559                        .emit()
2560                }
2561            }
2562        }
2563    }
2564
2565    pub trait ResultExt<T> {
2566        fn with_span(self, span: Span) -> Result<T, CheckerError>;
2567    }
2568
2569    impl<T, E> ResultExt<T> for Result<T, E>
2570    where
2571        E: Into<InferErr>,
2572    {
2573        fn with_span(self, span: Span) -> Result<T, CheckerError> {
2574            self.map_err(|err| CheckerError { kind: err.into(), span })
2575        }
2576    }
2577}