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