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    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 =
667                    self.check_terminator(&mut infcx, &mut env, terminator, last_stmt_span)?;
668                dbg::terminator!("end", terminator, infcx, env);
669
670                self.markers[bb] = Some(infcx.marker());
671                let term_span = last_stmt_span.unwrap_or(span);
672                self.check_successors(infcx, env, bb, term_span, successors)
673            })?;
674        }
675        Ok(())
676    }
677
678    fn check_assign_ty(
679        &mut self,
680        infcx: &mut InferCtxt,
681        env: &mut TypeEnv,
682        place: &Place,
683        ty: Ty,
684        span: Span,
685    ) -> InferResult {
686        let ty = infcx.hoister(true).hoist(&ty);
687        env.assign(&mut infcx.at(span), place, ty)
688    }
689
690    fn check_statement(
691        &mut self,
692        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
693        env: &mut TypeEnv,
694        stmt: &Statement<'tcx>,
695    ) -> Result {
696        let stmt_span = stmt.source_info.span;
697        match &stmt.kind {
698            StatementKind::Assign(place, rvalue) => {
699                let ty = self.check_rvalue(infcx, env, stmt_span, rvalue)?;
700                self.check_assign_ty(infcx, env, place, ty, stmt_span)
701                    .with_span(stmt_span)?;
702            }
703            StatementKind::SetDiscriminant { .. } => {
704                // TODO(nilehmann) double check here that the place is unfolded to
705                // the correct variant. This should be guaranteed by rustc
706            }
707            StatementKind::FakeRead(_) => {
708                // TODO(nilehmann) fake reads should be folding points
709            }
710            StatementKind::AscribeUserType(_, _) => {
711                // User ascriptions affect nll, but no refinement type checking.
712                // Maybe we can use this to associate refinement type to locals.
713            }
714            StatementKind::PlaceMention(_) => {
715                // Place mentions are a no-op used to detect uses of unsafe that would
716                // otherwise be optimized away.
717            }
718            StatementKind::Nop => {}
719            StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(op)) => {
720                // Currently, we only have the `assume` intrinsic, which if we're to trust rustc should be a NOP.
721                // TODO: There may be a use-case to actually "assume" the bool index associated with the operand,
722                // i.e. to strengthen the `rcx` / `env` with the assumption that the bool-index is in fact `true`...
723                let _ = self
724                    .check_operand(infcx, env, stmt_span, op)
725                    .with_span(stmt_span)?;
726            }
727        }
728        Ok(())
729    }
730
731    fn is_exit_block(&self, bb: BasicBlock) -> bool {
732        let data = &self.body.basic_blocks[bb];
733        let is_no_op = data.statements.iter().all(Statement::is_nop);
734        let is_ret = match &data.terminator {
735            None => false,
736            Some(term) => term.is_return(),
737        };
738        is_no_op && is_ret
739    }
740
741    /// For `check_terminator`, the output `Vec<BasicBlock, Guard>` denotes,
742    /// - `BasicBlock` "successors" of the current terminator, and
743    /// - `Guard` are extra control information from, e.g. the `SwitchInt` (or `Assert`) you can assume when checking the corresponding successor.
744    fn check_terminator(
745        &mut self,
746        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
747        env: &mut TypeEnv,
748        terminator: &Terminator<'tcx>,
749        last_stmt_span: Option<Span>,
750    ) -> Result<Vec<(BasicBlock, Guard)>> {
751        let source_info = terminator.source_info;
752        let terminator_span = source_info.span;
753        match &terminator.kind {
754            TerminatorKind::Return => {
755                self.check_ret(infcx, env, last_stmt_span.unwrap_or(terminator_span))?;
756                Ok(vec![])
757            }
758            TerminatorKind::Unreachable => Ok(vec![]),
759            TerminatorKind::CoroutineDrop => Ok(vec![]),
760            TerminatorKind::Goto { target } => Ok(vec![(*target, Guard::None)]),
761            TerminatorKind::Yield { resume, resume_arg, .. } => {
762                if let Some(resume_ty) = self.resume_ty.clone() {
763                    self.check_assign_ty(infcx, env, resume_arg, resume_ty, terminator_span)
764                        .with_span(terminator_span)?;
765                } else {
766                    bug!("yield in non-generator function");
767                }
768                Ok(vec![(*resume, Guard::None)])
769            }
770            TerminatorKind::SwitchInt { discr, targets } => {
771                let discr_ty = self
772                    .check_operand(infcx, env, terminator_span, discr)
773                    .with_span(terminator_span)?;
774                if discr_ty.is_integral() || discr_ty.is_bool() || discr_ty.is_char() {
775                    Ok(Self::check_if(&discr_ty, targets))
776                } else {
777                    Ok(self.check_match(infcx, env, &discr_ty, targets, terminator_span))
778                }
779            }
780            TerminatorKind::Call { kind, args, destination, target, .. } => {
781                let actuals = self
782                    .check_operands(infcx, env, terminator_span, args)
783                    .with_span(terminator_span)?;
784                let ret = match kind {
785                    mir::CallKind::FnDef { resolved_id, resolved_args, .. } => {
786                        let fn_sig = self.genv.fn_sig(*resolved_id).with_span(terminator_span)?;
787                        let generic_args = instantiate_args_for_fun_call(
788                            self.genv,
789                            self.checker_id.root_id().to_def_id(),
790                            *resolved_id,
791                            &resolved_args.lowered,
792                        )
793                        .with_span(terminator_span)?;
794                        self.check_call(
795                            infcx,
796                            env,
797                            terminator_span,
798                            Some(*resolved_id),
799                            fn_sig,
800                            &generic_args,
801                            &actuals,
802                        )?
803                        .output
804                    }
805                    mir::CallKind::FnPtr { operand, .. } => {
806                        let ty = self
807                            .check_operand(infcx, env, terminator_span, operand)
808                            .with_span(terminator_span)?;
809                        if let TyKind::Indexed(BaseTy::FnPtr(fn_sig), _) = infcx.unpack(&ty).kind()
810                        {
811                            self.check_call(
812                                infcx,
813                                env,
814                                terminator_span,
815                                None,
816                                EarlyBinder(fn_sig.clone()),
817                                &[],
818                                &actuals,
819                            )?
820                            .output
821                        } else {
822                            bug!("TODO: fnptr call {ty:?}")
823                        }
824                    }
825                };
826
827                let name = destination.name(&self.body.local_names);
828                let ret = infcx.unpack_at_name(name, &ret);
829                infcx.assume_invariants(&ret);
830
831                env.assign(&mut infcx.at(terminator_span), destination, ret)
832                    .with_span(terminator_span)?;
833
834                if let Some(target) = target {
835                    Ok(vec![(*target, Guard::None)])
836                } else {
837                    Ok(vec![])
838                }
839            }
840            TerminatorKind::Assert { cond, expected, target, msg } => {
841                Ok(vec![(
842                    *target,
843                    self.check_assert(infcx, env, terminator_span, cond, *expected, msg)
844                        .with_span(terminator_span)?,
845                )])
846            }
847            TerminatorKind::Drop { place, target, .. } => {
848                let _ = env.move_place(&mut infcx.at(terminator_span), place);
849                Ok(vec![(*target, Guard::None)])
850            }
851            TerminatorKind::FalseEdge { real_target, .. } => Ok(vec![(*real_target, Guard::None)]),
852            TerminatorKind::FalseUnwind { real_target, .. } => {
853                Ok(vec![(*real_target, Guard::None)])
854            }
855            TerminatorKind::UnwindResume => bug!("TODO: implement checking of cleanup code"),
856        }
857    }
858
859    fn check_ret(
860        &mut self,
861        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
862        env: &mut TypeEnv,
863        span: Span,
864    ) -> Result {
865        let obligations = infcx
866            .at(span)
867            .ensure_resolved_evars(|infcx| {
868                let ret_place_ty = env.lookup_place(infcx, Place::RETURN)?;
869                let output = self
870                    .fn_sig
871                    .output
872                    .replace_bound_refts_with(|sort, mode, _| infcx.fresh_infer_var(sort, mode));
873                let obligations =
874                    infcx.subtyping_with_env(env, &ret_place_ty, &output.ret, ConstrReason::Ret)?;
875
876                env.check_ensures(infcx, &output.ensures, ConstrReason::Ret)?;
877
878                Ok(obligations)
879            })
880            .with_span(span)?;
881
882        self.check_coroutine_obligations(infcx, obligations)
883    }
884
885    #[expect(clippy::too_many_arguments)]
886    fn check_call(
887        &mut self,
888        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
889        env: &mut TypeEnv,
890        span: Span,
891        callee_def_id: Option<DefId>,
892        fn_sig: EarlyBinder<PolyFnSig>,
893        generic_args: &[GenericArg],
894        actuals: &[Ty],
895    ) -> Result<ResolvedCall> {
896        let genv = self.genv;
897        let tcx = genv.tcx();
898
899        let actuals =
900            unfold_local_ptrs(infcx, env, fn_sig.skip_binder_ref(), actuals).with_span(span)?;
901        let actuals = infer_under_mut_ref_hack(infcx, &actuals, fn_sig.skip_binder_ref());
902        infcx.push_evar_scope();
903
904        // Replace holes in generic arguments with fresh inference variables
905        let generic_args = infcx.instantiate_generic_args(generic_args);
906
907        // Generate fresh inference variables for refinement arguments
908        let early_refine_args = match callee_def_id {
909            Some(callee_def_id) => {
910                infcx
911                    .instantiate_refine_args(callee_def_id, &generic_args)
912                    .with_span(span)?
913            }
914            None => rty::List::empty(),
915        };
916
917        let clauses = match callee_def_id {
918            Some(callee_def_id) => {
919                genv.predicates_of(callee_def_id)
920                    .with_span(span)?
921                    .predicates()
922                    .instantiate(tcx, &generic_args, &early_refine_args)
923            }
924            None => crate::rty::List::empty(),
925        };
926
927        let (clauses, fn_clauses) = Clause::split_off_fn_trait_clauses(self.genv, &clauses);
928        infcx
929            .at(span)
930            .check_non_closure_clauses(&clauses, ConstrReason::Call)
931            .with_span(span)?;
932
933        for fn_trait_pred in &fn_clauses {
934            self.check_fn_trait_clause(infcx, fn_trait_pred, span)?;
935        }
936
937        // Instantiate function signature and normalize it
938        let late_refine_args = vec![];
939        let fn_sig = fn_sig
940            .instantiate(tcx, &generic_args, &early_refine_args)
941            .replace_bound_vars(
942                |_| rty::ReErased,
943                |sort, mode, _| infcx.fresh_infer_var(sort, mode),
944            );
945
946        let fn_sig = fn_sig
947            .deeply_normalize(&mut infcx.at(span))
948            .with_span(span)?;
949
950        let mut at = infcx.at(span);
951
952        if let Some(callee_def_id) = callee_def_id
953            && genv.def_kind(callee_def_id).is_fn_like()
954        {
955            let callee_no_panic = fn_sig.no_panic();
956            let callee_inferred_spec = genv.inferred_no_panic(callee_def_id);
957
958            // The call-graph and fixpoint iteration are over `Instance`s, but the output map only
959            // retains non-mono (identity) instances (that we can identify with a `DefId`). A concrete
960            // callee (e.g., `Vec::<i32>::push`) is a mono instance whose spec is not directly queryable
961            // via inferred_no_panic. However, if the caller is `WillNotPanic`, the fixpoint already
962            // accounted for all its concrete callees: any `MightPanic` callee would have propagated up
963            // and marked the caller `MightPanic(Transitive)`. So the caller's `WillNotPanic` proves all
964            // its concrete callees are no-panic, even those whose mono spec is absent from the output
965            // map. We could potentially key specs by `Instance`, but we also need to be able to recover
966            // the resolution to a mono-instance here.
967            let inferred_panic_expr = if let CheckerId::DefId(caller_id) = self.checker_id
968                && genv.inferred_no_panic(caller_id) == PanicSpec::WillNotPanic
969            {
970                Expr::tt()
971            } else if callee_inferred_spec == PanicSpec::WillNotPanic {
972                Expr::tt()
973            } else {
974                Expr::ff()
975            };
976
977            at.check_pred(
978                Expr::implies(
979                    self.fn_sig.no_panic(),
980                    Expr::or(callee_no_panic, inferred_panic_expr),
981                ),
982                ConstrReason::NoPanic(callee_def_id, callee_inferred_spec),
983            );
984        }
985
986        // Check requires predicates
987        for requires in fn_sig.requires() {
988            at.check_pred(requires, ConstrReason::Call);
989        }
990
991        // Check arguments
992        for (actual, formal) in iter::zip(actuals, fn_sig.inputs()) {
993            at.subtyping_with_env(env, &actual, formal, ConstrReason::Call)
994                .with_span(span)?;
995        }
996
997        infcx.pop_evar_scope().with_span(span)?;
998        env.fully_resolve_evars(infcx);
999
1000        let output = infcx
1001            .fully_resolve_evars(&fn_sig.output)
1002            .replace_bound_refts_with(|sort, _, kind| {
1003                Expr::fvar(infcx.define_bound_reft_var(sort, kind))
1004            });
1005
1006        env.assume_ensures(infcx, &output.ensures, span);
1007        fold_local_ptrs(infcx, env, span).with_span(span)?;
1008
1009        Ok(ResolvedCall {
1010            output: output.ret,
1011            _early_args: early_refine_args
1012                .into_iter()
1013                .map(|arg| infcx.fully_resolve_evars(arg))
1014                .collect(),
1015            _late_args: late_refine_args
1016                .into_iter()
1017                .map(|arg| infcx.fully_resolve_evars(&arg))
1018                .collect(),
1019        })
1020    }
1021
1022    fn check_coroutine_obligations(
1023        &mut self,
1024        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1025        obligs: Vec<Binder<CoroutineObligPredicate>>,
1026    ) -> Result {
1027        for oblig in obligs {
1028            // FIXME(nilehmann) we shouldn't be skipping this binder
1029            let oblig = oblig.skip_binder();
1030
1031            #[expect(clippy::disallowed_methods, reason = "coroutines cannot be extern speced")]
1032            let def_id = oblig.def_id.expect_local();
1033            let span = self.genv.tcx().def_span(def_id);
1034            let body = self.genv.mir(def_id).with_span(span)?;
1035            Checker::run(
1036                infcx.change_item(def_id, &body.infcx),
1037                def_id,
1038                self.inherited.reborrow(),
1039                oblig.to_poly_fn_sig(),
1040            )?;
1041        }
1042        Ok(())
1043    }
1044
1045    fn find_self_ty_fn_sig(
1046        &self,
1047        self_ty: rustc_middle::ty::Ty<'tcx>,
1048        span: Span,
1049    ) -> Result<PolyFnSig> {
1050        let tcx = self.genv.tcx();
1051        let mut def_id = Some(self.checker_id.root_id().to_def_id());
1052        while let Some(did) = def_id {
1053            let generic_predicates = self
1054                .genv
1055                .predicates_of(did)
1056                .with_span(span)?
1057                .instantiate_identity();
1058            let predicates = generic_predicates.predicates;
1059
1060            for poly_fn_trait_pred in Clause::split_off_fn_trait_clauses(self.genv, &predicates).1 {
1061                if poly_fn_trait_pred.skip_binder_ref().self_ty.to_rustc(tcx) == self_ty {
1062                    return Ok(poly_fn_trait_pred.map(|fn_trait_pred| fn_trait_pred.fndef_sig()));
1063                }
1064            }
1065            // Continue to the parent if we didn't find a match
1066            def_id = generic_predicates.parent;
1067        }
1068
1069        span_bug!(
1070            span,
1071            "cannot find self_ty_fn_sig for {:?} with self_ty = {self_ty:?}",
1072            self.checker_id
1073        );
1074    }
1075
1076    fn check_fn_trait_clause(
1077        &mut self,
1078        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1079        poly_fn_trait_pred: &Binder<FnTraitPredicate>,
1080        span: Span,
1081    ) -> Result {
1082        let self_ty = poly_fn_trait_pred
1083            .skip_binder_ref()
1084            .self_ty
1085            .as_bty_skipping_existentials();
1086        let oblig_sig = poly_fn_trait_pred.map_ref(|fn_trait_pred| fn_trait_pred.fndef_sig());
1087        match self_ty {
1088            Some(BaseTy::Closure(def_id, _, _, _)) => {
1089                let Some(poly_sig) = self.inherited.closures.get(def_id).cloned() else {
1090                    span_bug!(span, "missing template for closure {def_id:?}");
1091                };
1092                check_fn_subtyping(infcx, SubFn::Mono(poly_sig.clone()), &oblig_sig, span)
1093                    .with_span(span)?;
1094            }
1095            Some(BaseTy::FnDef(def_id, args)) => {
1096                // Generates "function subtyping" obligations between the (super-type) `oblig_sig` in the `fn_trait_pred`
1097                // and the (sub-type) corresponding to the signature of `def_id + args`.
1098                // See `tests/neg/surface/fndef00.rs`
1099                let sub_sig = self.genv.fn_sig(def_id).with_span(span)?;
1100                check_fn_subtyping(
1101                    infcx,
1102                    SubFn::Poly(*def_id, sub_sig, args.clone()),
1103                    &oblig_sig,
1104                    span,
1105                )
1106                .with_span(span)?;
1107            }
1108            Some(BaseTy::FnPtr(sub_sig)) => {
1109                check_fn_subtyping(infcx, SubFn::Mono(sub_sig.clone()), &oblig_sig, span)
1110                    .with_span(span)?;
1111            }
1112
1113            // Some(self_ty) => {
1114            Some(self_ty @ BaseTy::Param(_)) => {
1115                // Step 1. Find matching clause and turn it into a FnSig
1116                let tcx = self.genv.tcx();
1117                let self_ty = self_ty.to_rustc(tcx);
1118                let sub_sig = self.find_self_ty_fn_sig(self_ty, span)?;
1119                // Step 2. Issue the subtyping
1120                check_fn_subtyping(infcx, SubFn::Mono(sub_sig), &oblig_sig, span)
1121                    .with_span(span)?;
1122            }
1123            _ => {}
1124        }
1125        Ok(())
1126    }
1127
1128    fn check_assert(
1129        &mut self,
1130        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1131        env: &mut TypeEnv,
1132        terminator_span: Span,
1133        cond: &Operand<'tcx>,
1134        expected: bool,
1135        msg: &AssertKind,
1136    ) -> InferResult<Guard> {
1137        let ty = self.check_operand(infcx, env, terminator_span, cond)?;
1138        let TyKind::Indexed(BaseTy::Bool, idx) = ty.kind() else {
1139            tracked_span_bug!("unexpected ty `{ty:?}`");
1140        };
1141        let pred = if expected { idx.clone() } else { idx.not() };
1142
1143        let msg = match msg {
1144            AssertKind::DivisionByZero => "possible division by zero",
1145            AssertKind::BoundsCheck => "possible out-of-bounds access",
1146            AssertKind::RemainderByZero => "possible remainder with a divisor of zero",
1147            AssertKind::Overflow(mir::BinOp::Div) => "possible division with overflow",
1148            AssertKind::Overflow(mir::BinOp::Rem) => "possible reminder with overflow",
1149            AssertKind::Overflow(_) => return Ok(Guard::Pred(pred)),
1150        };
1151        infcx
1152            .at(terminator_span)
1153            .check_pred(&pred, ConstrReason::Assert(msg));
1154        Ok(Guard::Pred(pred))
1155    }
1156
1157    /// 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.
1158    /// See <https://github.com/flux-rs/flux/pull/840#discussion_r1786543174>
1159    fn check_if(discr_ty: &Ty, targets: &SwitchTargets) -> Vec<(BasicBlock, Guard)> {
1160        let mk = |bits| {
1161            match discr_ty.kind() {
1162                TyKind::Indexed(BaseTy::Bool, idx) => {
1163                    if bits == 0 {
1164                        idx.not()
1165                    } else {
1166                        idx.clone()
1167                    }
1168                }
1169                TyKind::Indexed(bty @ (BaseTy::Int(_) | BaseTy::Uint(_) | BaseTy::Char), idx) => {
1170                    Expr::eq(idx.clone(), Expr::from_bits(bty, bits))
1171                }
1172                _ => tracked_span_bug!("unexpected discr_ty {:?}", discr_ty),
1173            }
1174        };
1175
1176        let mut successors = vec![];
1177
1178        for (bits, bb) in targets.iter() {
1179            successors.push((bb, Guard::Pred(mk(bits))));
1180        }
1181        let otherwise = Expr::and_from_iter(targets.iter().map(|(bits, _)| mk(bits).not()));
1182        successors.push((targets.otherwise(), Guard::Pred(otherwise)));
1183
1184        successors
1185    }
1186
1187    fn check_match(
1188        &mut self,
1189        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1190        env: &mut TypeEnv,
1191        discr_ty: &Ty,
1192        targets: &SwitchTargets,
1193        span: Span,
1194    ) -> Vec<(BasicBlock, Guard)> {
1195        let (adt_def, place) = discr_ty.expect_discr();
1196        let idx = if let Ok(ty) = env.lookup_place(&mut infcx.at(span), place)
1197            && let TyKind::Indexed(_, idx) = ty.kind()
1198        {
1199            Some(idx.clone())
1200        } else {
1201            None
1202        };
1203
1204        let mut successors = vec![];
1205        let mut remaining: FxHashMap<u128, VariantIdx> = adt_def
1206            .discriminants()
1207            .map(|(idx, discr)| (discr, idx))
1208            .collect();
1209        for (bits, bb) in targets.iter() {
1210            let variant_idx = remaining
1211                .remove(&bits)
1212                .expect("value doesn't correspond to any variant");
1213            successors.push((bb, Guard::Match(place.clone(), variant_idx)));
1214        }
1215        let guard = if remaining.len() == 1 {
1216            // If there's only one variant left, we know for sure that this is the one, so can force an unfold
1217            let (_, variant_idx) = remaining
1218                .into_iter()
1219                .next()
1220                .unwrap_or_else(|| tracked_span_bug!());
1221            Guard::Match(place.clone(), variant_idx)
1222        } else if adt_def.sort_def().is_reflected()
1223            && let Some(idx) = idx
1224        {
1225            // If there's more than one variant left, we can only assume the `is_ctor` holds for one of them
1226            let mut cases = vec![];
1227            for (_, variant_idx) in remaining {
1228                let did = adt_def.did();
1229                cases.push(rty::Expr::is_ctor(did, variant_idx, idx.clone()));
1230            }
1231            Guard::Pred(Expr::or_from_iter(cases))
1232        } else {
1233            Guard::None
1234        };
1235        successors.push((targets.otherwise(), guard));
1236
1237        successors
1238    }
1239
1240    fn check_successors(
1241        &mut self,
1242        mut infcx: InferCtxt<'_, 'genv, 'tcx>,
1243        env: TypeEnv,
1244        from: BasicBlock,
1245        terminator_span: Span,
1246        successors: Vec<(BasicBlock, Guard)>,
1247    ) -> Result {
1248        for (target, guard) in successors {
1249            let mut infcx = infcx.branch();
1250            let mut env = env.clone();
1251            match guard {
1252                Guard::None => {}
1253                Guard::Pred(expr) => {
1254                    infcx.assume_pred(&expr);
1255                }
1256                Guard::Match(place, variant_idx) => {
1257                    env.downcast(&mut infcx.at(terminator_span), &place, variant_idx)
1258                        .with_span(terminator_span)?;
1259                }
1260            }
1261            self.check_ghost_statements_at(
1262                &mut infcx,
1263                &mut env,
1264                Point::Edge(from, target),
1265                terminator_span,
1266            )?;
1267            self.check_goto(infcx, env, terminator_span, target)?;
1268        }
1269        Ok(())
1270    }
1271
1272    fn check_goto(
1273        &mut self,
1274        mut infcx: InferCtxt<'_, 'genv, 'tcx>,
1275        mut env: TypeEnv,
1276        span: Span,
1277        target: BasicBlock,
1278    ) -> Result {
1279        if self.is_exit_block(target) {
1280            // We inline *exit basic blocks* (i.e., that just return) because this typically
1281            // gives us better a better error span.
1282            let mut location = Location { block: target, statement_index: 0 };
1283            for _ in &self.body.basic_blocks[target].statements {
1284                self.check_ghost_statements_at(
1285                    &mut infcx,
1286                    &mut env,
1287                    Point::BeforeLocation(location),
1288                    span,
1289                )?;
1290                location = location.successor_within_block();
1291            }
1292            self.check_ghost_statements_at(
1293                &mut infcx,
1294                &mut env,
1295                Point::BeforeLocation(location),
1296                span,
1297            )?;
1298            self.check_ret(&mut infcx, &mut env, span)
1299        } else if let Some(real_target) = self.is_dummy_join(target) {
1300            self.check_goto(infcx, env, span, real_target)
1301        } else if self.body.is_join_point(target) {
1302            if M::check_goto_join_point(self, infcx, env, span, target)? {
1303                self.queue.insert(target);
1304            }
1305            Ok(())
1306        } else {
1307            self.check_basic_block(infcx, env, target)
1308        }
1309    }
1310
1311    /// A dummy-join-block is a BasicBlock that has
1312    /// 1. MULTIPLE incoming edges,
1313    /// 2. SINGLE outgoing edge,
1314    /// 3. ONLY no-op statements, and
1315    /// 4. NO ghosts statements.
1316    ///
1317    /// (1) is because we want to avoid "spurious joins"
1318    /// but also, without it, we have problems with
1319    /// degenerate loops like (e.g. `const_generics/loop.rs`).
1320    ///
1321    /// We can "skip" such blocks and jump straight to
1322    /// the first (transitively reachable) non-dummy
1323    /// successor, aka the "real" successor, which allows
1324    /// us to avoid emitting KVars for spurious joins.
1325    fn is_dummy_join(&self, bb: BasicBlock) -> Option<BasicBlock> {
1326        if self.body.is_join_point(bb)
1327            && self.body.basic_blocks[bb]
1328                .statements
1329                .iter()
1330                .all(Statement::is_nop)
1331            && let Some(TerminatorKind::Goto { target: real_target }) = &self.body.basic_blocks[bb]
1332                .terminator
1333                .as_ref()
1334                .map(|terminator| &terminator.kind)
1335            && self.no_ghosts_at(bb, *real_target)
1336        {
1337            Some(*real_target)
1338        } else {
1339            None
1340        }
1341    }
1342
1343    fn no_ghosts_at(&self, bb: BasicBlock, real_target: BasicBlock) -> bool {
1344        let Some(ghosts) = self.inherited.ghost_stmts.get(&self.checker_id) else {
1345            return true;
1346        };
1347
1348        let mut res = ghosts
1349            .statements_at(Point::Edge(bb, real_target))
1350            .next()
1351            .is_none();
1352
1353        let mut location = Location { block: bb, statement_index: 0 };
1354        for _ in &self.body.basic_blocks[bb].statements {
1355            res = res
1356                && ghosts
1357                    .statements_at(Point::BeforeLocation(location))
1358                    .next()
1359                    .is_none();
1360            location = location.successor_within_block();
1361        }
1362        res = res
1363            && ghosts
1364                .statements_at(Point::BeforeLocation(location))
1365                .next()
1366                .is_none();
1367        res
1368    }
1369
1370    fn closure_template(
1371        &mut self,
1372        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1373        env: &mut TypeEnv,
1374        stmt_span: Span,
1375        args: &flux_rustc_bridge::ty::GenericArgs,
1376        operands: &[Operand<'tcx>],
1377    ) -> InferResult<(Vec<Ty>, PolyFnSig)> {
1378        let upvar_tys = self
1379            .check_operands(infcx, env, stmt_span, operands)?
1380            .into_iter()
1381            .map(|ty| {
1382                if let TyKind::Ptr(PtrKind::Mut(re), path) = ty.kind() {
1383                    env.ptr_to_ref(
1384                        &mut infcx.at(stmt_span),
1385                        ConstrReason::Other,
1386                        *re,
1387                        path,
1388                        PtrToRefBound::Infer,
1389                    )
1390                } else {
1391                    Ok(ty.clone())
1392                }
1393            })
1394            .try_collect_vec()?;
1395
1396        let closure_args = args.as_closure();
1397        let ty = closure_args.sig_as_fn_ptr_ty();
1398
1399        if let flux_rustc_bridge::ty::TyKind::FnPtr(poly_sig) = ty.kind() {
1400            let poly_sig = poly_sig.unpack_closure_sig();
1401            let poly_sig = self.refine_with_holes(&poly_sig)?;
1402            let poly_sig = poly_sig.hoist_input_binders();
1403            let poly_sig = poly_sig
1404                .replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind));
1405
1406            Ok((upvar_tys, poly_sig))
1407        } else {
1408            bug!("check_rvalue: closure: expected fn_ptr ty, found {ty:?} in {args:?}");
1409        }
1410    }
1411
1412    fn check_closure_body(
1413        &mut self,
1414        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1415        did: &DefId,
1416        upvar_tys: &[Ty],
1417        args: &flux_rustc_bridge::ty::GenericArgs,
1418        poly_sig: &PolyFnSig,
1419    ) -> Result {
1420        let genv = self.genv;
1421        let tcx = genv.tcx();
1422        #[expect(clippy::disallowed_methods, reason = "closures cannot be extern speced")]
1423        let closure_id = did.expect_local();
1424        let span = tcx.def_span(closure_id);
1425        let body = genv.mir(closure_id).with_span(span)?;
1426        let no_panic = self.genv.no_panic(*did);
1427        let closure_sig = rty::to_closure_sig(tcx, closure_id, upvar_tys, args, poly_sig, no_panic);
1428        Checker::run(
1429            infcx.change_item(closure_id, &body.infcx),
1430            closure_id,
1431            self.inherited.reborrow(),
1432            closure_sig,
1433        )
1434    }
1435
1436    fn check_rvalue_closure(
1437        &mut self,
1438        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1439        env: &mut TypeEnv,
1440        stmt_span: Span,
1441        did: &DefId,
1442        args: &flux_rustc_bridge::ty::GenericArgs,
1443        operands: &[Operand<'tcx>],
1444    ) -> Result<Ty> {
1445        // (1) Create the closure template
1446        let (upvar_tys, poly_sig) = self
1447            .closure_template(infcx, env, stmt_span, args, operands)
1448            .with_span(stmt_span)?;
1449        // (2) Check the closure body against the template
1450        self.check_closure_body(infcx, did, &upvar_tys, args, &poly_sig)?;
1451        // (3) "Save" the closure type in the `closures` map
1452        self.inherited.closures.insert(*did, poly_sig);
1453        // (4) Return the closure type
1454        let no_panic = self.genv.no_panic(*did);
1455        Ok(Ty::closure(*did, upvar_tys, args, no_panic))
1456    }
1457
1458    fn check_rvalue(
1459        &mut self,
1460        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1461        env: &mut TypeEnv,
1462        stmt_span: Span,
1463        rvalue: &Rvalue<'tcx>,
1464    ) -> Result<Ty> {
1465        let genv = self.genv;
1466        match rvalue {
1467            Rvalue::Use(operand) => {
1468                self.check_operand(infcx, env, stmt_span, operand)
1469                    .with_span(stmt_span)
1470            }
1471            Rvalue::Repeat(operand, c) => {
1472                let ty = self
1473                    .check_operand(infcx, env, stmt_span, operand)
1474                    .with_span(stmt_span)?;
1475                let arr_ty = ty
1476                    .with_holes()
1477                    .replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind));
1478                infcx
1479                    .at(stmt_span)
1480                    .subtyping_with_env(env, &ty, &arr_ty, ConstrReason::Other)
1481                    .with_span(stmt_span)?;
1482                Ok(Ty::array(arr_ty, c.clone()))
1483            }
1484            Rvalue::Ref(r, BorrowKind::Mut { .. }, place) => {
1485                env.borrow(&mut infcx.at(stmt_span), *r, Mutability::Mut, place)
1486                    .with_span(stmt_span)
1487            }
1488            Rvalue::Ref(r, BorrowKind::Shared | BorrowKind::Fake(..), place) => {
1489                env.borrow(&mut infcx.at(stmt_span), *r, Mutability::Not, place)
1490                    .with_span(stmt_span)
1491            }
1492
1493            Rvalue::RawPtr(mir::RawPtrKind::FakeForPtrMetadata, place) => {
1494                // see tests/tests/neg/surface/slice02.rs for what happens without unfolding here.
1495                env.unfold(infcx, place, stmt_span).with_span(stmt_span)?;
1496                let ty = env
1497                    .lookup_place(&mut infcx.at(stmt_span), place)
1498                    .with_span(stmt_span)?;
1499                let ty = BaseTy::RawPtrMetadata(ty).to_ty();
1500                Ok(ty)
1501            }
1502            Rvalue::RawPtr(kind, place) => {
1503                // ignore any refinements on the type stored at place
1504                let ty = &env.lookup_rust_ty(genv, place).with_span(stmt_span)?;
1505                let ty = self.refine_default(ty).with_span(stmt_span)?;
1506                let ty = BaseTy::RawPtr(ty, kind.to_mutbl_lossy()).to_ty();
1507                Ok(ty)
1508            }
1509            Rvalue::Cast(kind, op, to) => {
1510                let from = self
1511                    .check_operand(infcx, env, stmt_span, op)
1512                    .with_span(stmt_span)?;
1513                self.check_cast(infcx, env, stmt_span, *kind, &from, to)
1514                    .with_span(stmt_span)
1515            }
1516            Rvalue::BinaryOp(bin_op, op1, op2) => {
1517                self.check_binary_op(infcx, env, stmt_span, *bin_op, op1, op2)
1518                    .with_span(stmt_span)
1519            }
1520
1521            Rvalue::UnaryOp(UnOp::PtrMetadata, Operand::Copy(place))
1522            | Rvalue::UnaryOp(UnOp::PtrMetadata, Operand::Move(place)) => {
1523                self.check_raw_ptr_metadata(infcx, env, stmt_span, place)
1524            }
1525            Rvalue::UnaryOp(un_op, op) => {
1526                self.check_unary_op(infcx, env, stmt_span, *un_op, op)
1527                    .with_span(stmt_span)
1528            }
1529            Rvalue::Discriminant(place) => {
1530                let ty = env
1531                    .lookup_place(&mut infcx.at(stmt_span), place)
1532                    .with_span(stmt_span)?;
1533                // HACK(nilehmann, mut-ref-unfolding) place should be unfolded here.
1534                let (adt_def, ..) = ty
1535                    .as_bty_skipping_existentials()
1536                    .unwrap_or_else(|| tracked_span_bug!())
1537                    .expect_adt();
1538                Ok(Ty::discr(adt_def.clone(), place.clone()))
1539            }
1540            Rvalue::Aggregate(
1541                AggregateKind::Adt(def_id, variant_idx, args, _, field_idx),
1542                operands,
1543            ) => {
1544                let actuals = self
1545                    .check_operands(infcx, env, stmt_span, operands)
1546                    .with_span(stmt_span)?;
1547                let sig = genv
1548                    .variant_sig(*def_id, *variant_idx)
1549                    .with_span(stmt_span)?
1550                    .ok_or_query_err(*def_id)
1551                    .with_span(stmt_span)?
1552                    .to_poly_fn_sig(*field_idx);
1553
1554                let args = instantiate_args_for_constructor(
1555                    genv,
1556                    self.checker_id.root_id().to_def_id(),
1557                    *def_id,
1558                    args,
1559                )
1560                .with_span(stmt_span)?;
1561                self.check_call(infcx, env, stmt_span, Some(*def_id), sig, &args, &actuals)
1562                    .map(|resolved_call| resolved_call.output)
1563            }
1564            Rvalue::Aggregate(AggregateKind::Array(arr_ty), operands) => {
1565                let args = self
1566                    .check_operands(infcx, env, stmt_span, operands)
1567                    .with_span(stmt_span)?;
1568                let arr_ty = self.refine_with_holes(arr_ty).with_span(stmt_span)?;
1569                self.check_mk_array(infcx, env, stmt_span, &args, arr_ty)
1570                    .with_span(stmt_span)
1571            }
1572            Rvalue::Aggregate(AggregateKind::Tuple, args) => {
1573                let tys = self
1574                    .check_operands(infcx, env, stmt_span, args)
1575                    .with_span(stmt_span)?;
1576                Ok(Ty::tuple(tys))
1577            }
1578            Rvalue::Aggregate(AggregateKind::Closure(did, args), operands) => {
1579                self.check_rvalue_closure(infcx, env, stmt_span, did, args, operands)
1580            }
1581            Rvalue::Aggregate(AggregateKind::Coroutine(did, args), ops) => {
1582                let coroutine_args = args.as_coroutine();
1583                let resume_ty = self
1584                    .refine_default(coroutine_args.resume_ty())
1585                    .with_span(stmt_span)?;
1586                let upvar_tys = self
1587                    .check_operands(infcx, env, stmt_span, ops)
1588                    .with_span(stmt_span)?;
1589                Ok(Ty::coroutine(*did, resume_ty, upvar_tys.into(), args.clone()))
1590            }
1591            Rvalue::ShallowInitBox(operand, _) => {
1592                self.check_operand(infcx, env, stmt_span, operand)
1593                    .with_span(stmt_span)?;
1594                Ty::mk_box_with_default_alloc(self.genv, Ty::uninit()).with_span(stmt_span)
1595            }
1596        }
1597    }
1598
1599    fn check_raw_ptr_metadata(
1600        &mut self,
1601        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1602        env: &mut TypeEnv,
1603        stmt_span: Span,
1604        place: &Place,
1605    ) -> Result<Ty> {
1606        let ty = env
1607            .lookup_place(&mut infcx.at(stmt_span), place)
1608            .with_span(stmt_span)?;
1609        let ty = match ty.kind() {
1610            TyKind::Indexed(BaseTy::RawPtrMetadata(ty), _)
1611            | TyKind::Indexed(BaseTy::Ref(_, ty, _), _) => ty,
1612            _ => tracked_span_bug!("check_metadata: bug! unexpected type `{ty:?}`"),
1613        };
1614        match ty.kind() {
1615            TyKind::Indexed(BaseTy::Array(_, len), _) => {
1616                let idx = Expr::from_const(self.genv.tcx(), len);
1617                Ok(Ty::indexed(BaseTy::Uint(UintTy::Usize), idx))
1618            }
1619            TyKind::Indexed(BaseTy::Slice(_), len) => {
1620                Ok(Ty::indexed(BaseTy::Uint(UintTy::Usize), len.clone()))
1621            }
1622            _ => Ok(Ty::unit()),
1623        }
1624    }
1625
1626    fn check_binary_op(
1627        &mut self,
1628        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1629        env: &mut TypeEnv,
1630        stmt_span: Span,
1631        bin_op: mir::BinOp,
1632        op1: &Operand<'tcx>,
1633        op2: &Operand<'tcx>,
1634    ) -> InferResult<Ty> {
1635        let ty1 = self.check_operand(infcx, env, stmt_span, op1)?;
1636        let ty2 = self.check_operand(infcx, env, stmt_span, op2)?;
1637
1638        match (ty1.kind(), ty2.kind()) {
1639            (TyKind::Indexed(bty1, idx1), TyKind::Indexed(bty2, idx2)) => {
1640                let rule =
1641                    primops::match_bin_op(bin_op, bty1, idx1, bty2, idx2, infcx.check_overflow);
1642                if let Some(pre) = rule.precondition {
1643                    infcx.at(stmt_span).check_pred(pre.pred, pre.reason);
1644                }
1645
1646                Ok(rule.output_type)
1647            }
1648            _ => tracked_span_bug!("incompatible types: `{ty1:?}` `{ty2:?}`"),
1649        }
1650    }
1651
1652    fn check_unary_op(
1653        &mut self,
1654        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1655        env: &mut TypeEnv,
1656        stmt_span: Span,
1657        un_op: mir::UnOp,
1658        op: &Operand<'tcx>,
1659    ) -> InferResult<Ty> {
1660        let ty = self.check_operand(infcx, env, stmt_span, op)?;
1661        match ty.kind() {
1662            TyKind::Indexed(bty, idx) => {
1663                let rule = primops::match_un_op(un_op, bty, idx, infcx.check_overflow);
1664                if let Some(pre) = rule.precondition {
1665                    infcx.at(stmt_span).check_pred(pre.pred, pre.reason);
1666                }
1667                Ok(rule.output_type)
1668            }
1669            _ => tracked_span_bug!("invalid type for unary operator `{un_op:?}` `{ty:?}`"),
1670        }
1671    }
1672
1673    fn check_mk_array(
1674        &mut self,
1675        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1676        env: &mut TypeEnv,
1677        stmt_span: Span,
1678        args: &[Ty],
1679        arr_ty: Ty,
1680    ) -> InferResult<Ty> {
1681        let arr_ty = infcx.ensure_resolved_evars(|infcx| {
1682            let arr_ty =
1683                arr_ty.replace_holes(|binders, kind| infcx.fresh_infer_var_for_hole(binders, kind));
1684
1685            let (arr_ty, pred) = arr_ty.unconstr();
1686            let mut at = infcx.at(stmt_span);
1687            at.check_pred(&pred, ConstrReason::Other);
1688            for ty in args {
1689                at.subtyping_with_env(env, ty, &arr_ty, ConstrReason::Other)?;
1690            }
1691            Ok(arr_ty)
1692        })?;
1693        let arr_ty = infcx.fully_resolve_evars(&arr_ty);
1694
1695        Ok(Ty::array(arr_ty, rty::Const::from_usize(self.genv.tcx(), args.len())))
1696    }
1697
1698    fn check_cast(
1699        &self,
1700        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1701        env: &mut TypeEnv,
1702        stmt_span: Span,
1703        kind: CastKind,
1704        from: &Ty,
1705        to: &ty::Ty,
1706    ) -> InferResult<Ty> {
1707        use ty::TyKind as RustTy;
1708        let ty = match kind {
1709            CastKind::PointerExposeProvenance => {
1710                match to.kind() {
1711                    RustTy::Int(int_ty) => Ty::int(*int_ty),
1712                    RustTy::Uint(uint_ty) => Ty::uint(*uint_ty),
1713                    _ => tracked_span_bug!("unsupported PointerExposeProvenance cast"),
1714                }
1715            }
1716            CastKind::IntToInt => {
1717                match (from.kind(), to.kind()) {
1718                    (Bool!(idx), RustTy::Int(int_ty)) => bool_int_cast(idx, *int_ty),
1719                    (Bool!(idx), RustTy::Uint(uint_ty)) => bool_uint_cast(idx, *uint_ty),
1720                    (Int!(int_ty1, idx), RustTy::Int(int_ty2)) => {
1721                        int_int_cast(idx, *int_ty1, *int_ty2)
1722                    }
1723                    (Uint!(uint_ty1, idx), RustTy::Uint(uint_ty2)) => {
1724                        uint_uint_cast(idx, *uint_ty1, *uint_ty2)
1725                    }
1726                    (Uint!(uint_ty, idx), RustTy::Int(int_ty)) => {
1727                        uint_int_cast(idx, *uint_ty, *int_ty)
1728                    }
1729                    (Int!(int_ty, idx), RustTy::Uint(uint_ty)) => {
1730                        int_uint_cast(idx, *int_ty, *uint_ty)
1731                    }
1732                    (TyKind::Discr(adt_def, _), RustTy::Int(int_ty)) => {
1733                        Self::discr_to_int_cast(adt_def, BaseTy::Int(*int_ty))
1734                    }
1735                    (TyKind::Discr(adt_def, _place), RustTy::Uint(uint_ty)) => {
1736                        Self::discr_to_int_cast(adt_def, BaseTy::Uint(*uint_ty))
1737                    }
1738                    (Char!(idx), RustTy::Uint(uint_ty)) => char_uint_cast(idx, *uint_ty),
1739                    (Uint!(_, idx), RustTy::Char) => uint_char_cast(idx),
1740                    _ => {
1741                        tracked_span_bug!("invalid int to int cast {from:?} --> {to:?}")
1742                    }
1743                }
1744            }
1745            CastKind::PointerCoercion(mir::PointerCast::Unsize) => {
1746                self.check_unsize_cast(infcx, env, stmt_span, from, to)?
1747            }
1748            CastKind::FloatToInt
1749            | CastKind::IntToFloat
1750            | CastKind::PtrToPtr
1751            | CastKind::PointerCoercion(mir::PointerCast::MutToConstPointer)
1752            | CastKind::PointerCoercion(mir::PointerCast::ClosureFnPointer)
1753            | CastKind::PointerWithExposedProvenance => self.refine_default(to)?,
1754            CastKind::PointerCoercion(mir::PointerCast::ReifyFnPointer) => {
1755                let to = self.refine_default(to)?;
1756                if let TyKind::Indexed(BaseTy::FnDef(def_id, args), _) = from.kind()
1757                    && let TyKind::Indexed(BaseTy::FnPtr(super_sig), _) = to.kind()
1758                {
1759                    let current_did = infcx.def_id;
1760                    let sub_sig =
1761                        SubFn::Poly(current_did, infcx.genv.fn_sig(*def_id)?, args.clone());
1762                    // TODO:CLOSURE:2 TODO(RJ) dicey maneuver? assumes that sig_b is unrefined?
1763                    check_fn_subtyping(infcx, sub_sig, super_sig, stmt_span)?;
1764                    to
1765                } else {
1766                    tracked_span_bug!("invalid cast from `{from:?}` to `{to:?}`")
1767                }
1768            }
1769        };
1770        Ok(ty)
1771    }
1772
1773    fn discr_to_int_cast(adt_def: &AdtDef, bty: BaseTy) -> Ty {
1774        // TODO: This could be a giant disjunction, maybe better (if less precise) to use the interval?
1775        let vals = adt_def
1776            .discriminants()
1777            .map(|(_, idx)| Expr::eq(Expr::nu(), Expr::from_bits(&bty, idx)))
1778            .collect_vec();
1779        Ty::exists_with_constr(bty, Expr::or_from_iter(vals))
1780    }
1781
1782    fn check_unsize_cast(
1783        &self,
1784        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1785        env: &mut TypeEnv,
1786        span: Span,
1787        src: &Ty,
1788        dst: &ty::Ty,
1789    ) -> InferResult<Ty> {
1790        // Convert `ptr` to `&mut`
1791        let src = if let TyKind::Ptr(PtrKind::Mut(re), path) = src.kind() {
1792            env.ptr_to_ref(
1793                &mut infcx.at(span),
1794                ConstrReason::Other,
1795                *re,
1796                path,
1797                PtrToRefBound::Identity,
1798            )?
1799        } else {
1800            src.clone()
1801        };
1802
1803        if let ty::TyKind::Ref(_, deref_ty, _) = dst.kind()
1804            && let ty::TyKind::Dynamic(..) = deref_ty.kind()
1805        {
1806            return Ok(self.refine_default(dst)?);
1807        }
1808
1809        // `&mut [T; n] -> &mut [T]` or `&[T; n] -> &[T]`
1810        if let TyKind::Indexed(BaseTy::Ref(_, deref_ty, _), _) = src.kind()
1811            && let TyKind::Indexed(BaseTy::Array(arr_ty, arr_len), _) = deref_ty.kind()
1812            && let ty::TyKind::Ref(re, _, mutbl) = dst.kind()
1813        {
1814            let idx = Expr::from_const(self.genv.tcx(), arr_len);
1815            Ok(Ty::mk_ref(*re, Ty::indexed(BaseTy::Slice(arr_ty.clone()), idx), *mutbl))
1816
1817        // `Box<[T; n]> -> Box<[T]>`
1818        } else if let TyKind::Indexed(BaseTy::Adt(adt_def, args), _) = src.kind()
1819            && adt_def.is_box()
1820            && let (deref_ty, alloc_ty) = args.box_args()
1821            && let TyKind::Indexed(BaseTy::Array(arr_ty, arr_len), _) = deref_ty.kind()
1822        {
1823            let idx = Expr::from_const(self.genv.tcx(), arr_len);
1824            Ok(Ty::mk_box(
1825                self.genv,
1826                Ty::indexed(BaseTy::Slice(arr_ty.clone()), idx),
1827                alloc_ty.clone(),
1828            )?)
1829        } else {
1830            Err(query_bug!("unsupported unsize cast from `{src:?}` to `{dst:?}`"))?
1831        }
1832    }
1833
1834    fn check_operands(
1835        &mut self,
1836        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1837        env: &mut TypeEnv,
1838        span: Span,
1839        operands: &[Operand<'tcx>],
1840    ) -> InferResult<Vec<Ty>> {
1841        operands
1842            .iter()
1843            .map(|op| self.check_operand(infcx, env, span, op))
1844            .try_collect()
1845    }
1846
1847    fn check_operand(
1848        &mut self,
1849        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
1850        env: &mut TypeEnv,
1851        span: Span,
1852        operand: &Operand<'tcx>,
1853    ) -> InferResult<Ty> {
1854        let ty = match operand {
1855            Operand::Copy(p) => env.lookup_place(&mut infcx.at(span), p)?,
1856            Operand::Move(p) => env.move_place(&mut infcx.at(span), p)?,
1857            Operand::Constant(c) => self.check_constant(infcx, c)?,
1858        };
1859        Ok(infcx.hoister(true).hoist(&ty))
1860    }
1861
1862    fn check_constant(
1863        &mut self,
1864        infcx: &InferCtxt<'_, 'genv, 'tcx>,
1865        constant: &ConstOperand<'tcx>,
1866    ) -> QueryResult<Ty> {
1867        use rustc_middle::mir::Const;
1868        match constant.const_ {
1869            Const::Ty(ty, cst) => self.check_ty_const(constant, cst, ty)?,
1870            Const::Val(val, ty) => self.check_const_val(val, ty)?,
1871            Const::Unevaluated(uneval, ty) => {
1872                self.check_uneval_const(infcx, constant, uneval, ty)?
1873            }
1874        }
1875        .map_or_else(|| self.refine_default(&constant.ty), Ok)
1876    }
1877
1878    fn check_ty_const(
1879        &mut self,
1880        constant: &ConstOperand<'tcx>,
1881        cst: rustc_middle::ty::Const<'tcx>,
1882        ty: rustc_middle::ty::Ty<'tcx>,
1883    ) -> QueryResult<Option<Ty>> {
1884        use rustc_middle::ty::ConstKind;
1885        match cst.kind() {
1886            ConstKind::Param(param) => {
1887                let idx = Expr::const_generic(param);
1888                let ctor = self
1889                    .default_refiner
1890                    .refine_ty_or_base(&constant.ty)?
1891                    .expect_base();
1892                Ok(Some(ctor.replace_bound_reft(&idx).to_ty()))
1893            }
1894            ConstKind::Value(val_tree) => {
1895                let val = self.genv.tcx().valtree_to_const_val(val_tree);
1896                Ok(self.check_const_val(val, ty)?)
1897            }
1898            _ => Ok(None),
1899        }
1900    }
1901
1902    fn check_const_val(
1903        &mut self,
1904        val: rustc_middle::mir::ConstValue,
1905        ty: rustc_middle::ty::Ty<'tcx>,
1906    ) -> QueryResult<Option<Ty>> {
1907        use rustc_middle::{mir::ConstValue, ty};
1908        match val {
1909            ConstValue::Scalar(scalar) => self.check_scalar(scalar, ty),
1910            ConstValue::ZeroSized if ty.is_unit() => Ok(Some(Ty::unit())),
1911            ConstValue::Slice { .. } => {
1912                if let ty::Ref(_, ref_ty, Mutability::Not) = ty.kind()
1913                    && ref_ty.is_str()
1914                    && let Some(data) = val.try_get_slice_bytes_for_diagnostics(self.genv.tcx())
1915                {
1916                    let str = String::from_utf8_lossy(data);
1917                    let idx = Expr::constant(Constant::Str(Symbol::intern(&str)));
1918                    Ok(Some(Ty::mk_ref(ReErased, Ty::indexed(BaseTy::Str, idx), Mutability::Not)))
1919                } else {
1920                    Ok(None)
1921                }
1922            }
1923            _ => Ok(None),
1924        }
1925    }
1926
1927    fn check_uneval_const(
1928        &mut self,
1929        infcx: &InferCtxt<'_, 'genv, 'tcx>,
1930        constant: &ConstOperand<'tcx>,
1931        uneval: rustc_middle::mir::UnevaluatedConst<'tcx>,
1932        ty: rustc_middle::ty::Ty<'tcx>,
1933    ) -> QueryResult<Option<Ty>> {
1934        // 1. Use template for promoted constants, if applicable
1935        if let Some(promoted) = uneval.promoted
1936            && let Some(ty) = self.promoted.get(promoted)
1937        {
1938            return Ok(Some(ty.clone()));
1939        }
1940
1941        // 2. `Genv::constant_info` cannot handle constants with generics, so, we evaluate
1942        //    them here. These mostly come from inline consts, e.g., `const { 1 + 1 }`, because
1943        //    the generic_const_items feature is unstable.
1944        if !uneval.args.is_empty() {
1945            let tcx = self.genv.tcx();
1946            let param_env = tcx.param_env(self.checker_id.root_id());
1947            let typing_env = infcx.region_infcx.typing_env(param_env);
1948            if let Ok(val) = tcx.const_eval_resolve(typing_env, uneval, constant.span) {
1949                return self.check_const_val(val, ty);
1950            } else {
1951                return Ok(None);
1952            }
1953        }
1954
1955        // 3. Try to see if we have `consant_info` for it.
1956        if let rty::TyOrBase::Base(ctor) = self.default_refiner.refine_ty_or_base(&constant.ty)?
1957            && let rty::ConstantInfo::Interpreted(idx, _) = self.genv.constant_info(uneval.def)?
1958        {
1959            return Ok(Some(ctor.replace_bound_reft(&idx).to_ty()));
1960        }
1961
1962        Ok(None)
1963    }
1964
1965    fn check_scalar(
1966        &mut self,
1967        scalar: rustc_middle::mir::interpret::Scalar,
1968        ty: rustc_middle::ty::Ty<'tcx>,
1969    ) -> QueryResult<Option<Ty>> {
1970        use rustc_middle::mir::interpret::{GlobalAlloc, Scalar};
1971        match scalar {
1972            Scalar::Int(scalar_int) => Ok(self.check_scalar_int(scalar_int, ty)),
1973            Scalar::Ptr(ptr, _) => {
1974                let alloc_id = ptr.provenance.alloc_id();
1975                if let GlobalAlloc::Static(def_id) = self.genv.tcx().global_alloc(alloc_id)
1976                    && let rty::StaticInfo::Known(ty) = self.genv.static_info(def_id)?
1977                    && !self.genv.tcx().is_mutable_static(def_id)
1978                // TODO: mutable statics!
1979                {
1980                    Ok(Some(Ty::mk_ref(ReErased, ty, Mutability::Not)))
1981                } else {
1982                    Ok(None)
1983                }
1984            }
1985        }
1986    }
1987
1988    fn check_scalar_int(
1989        &mut self,
1990        scalar: rustc_middle::ty::ScalarInt,
1991        ty: rustc_middle::ty::Ty<'tcx>,
1992    ) -> Option<Ty> {
1993        use flux_rustc_bridge::const_eval::{scalar_to_int, scalar_to_uint};
1994        use rustc_middle::ty;
1995
1996        let tcx = self.genv.tcx();
1997
1998        match ty.kind() {
1999            ty::Int(int_ty) => {
2000                let idx = Expr::constant(Constant::from(scalar_to_int(tcx, scalar, *int_ty)));
2001                Some(Ty::indexed(BaseTy::Int(*int_ty), idx))
2002            }
2003            ty::Uint(uint_ty) => {
2004                let idx = Expr::constant(Constant::from(scalar_to_uint(tcx, scalar, *uint_ty)));
2005                Some(Ty::indexed(BaseTy::Uint(*uint_ty), idx))
2006            }
2007            ty::Float(float_ty) => Some(Ty::float(*float_ty)),
2008            ty::Char => {
2009                let idx = Expr::constant(Constant::Char(scalar.try_into().unwrap()));
2010                Some(Ty::indexed(BaseTy::Char, idx))
2011            }
2012            ty::Bool => {
2013                let idx = Expr::constant(Constant::Bool(scalar.try_to_bool().unwrap()));
2014                Some(Ty::indexed(BaseTy::Bool, idx))
2015            }
2016            // ty::Tuple(tys) if tys.is_empty() => Constant::Unit,
2017            _ => None,
2018        }
2019    }
2020
2021    fn check_ghost_statements_at(
2022        &mut self,
2023        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2024        env: &mut TypeEnv,
2025        point: Point,
2026        span: Span,
2027    ) -> Result {
2028        bug::track_span(span, || {
2029            for stmt in self.ghost_stmts().statements_at(point) {
2030                self.check_ghost_statement(infcx, env, stmt, span)
2031                    .with_span(span)?;
2032            }
2033            Ok(())
2034        })
2035    }
2036
2037    fn check_ghost_statement(
2038        &mut self,
2039        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2040        env: &mut TypeEnv,
2041        stmt: &GhostStatement,
2042        span: Span,
2043    ) -> InferResult {
2044        dbg::statement!("start", stmt, infcx, env, span, &self);
2045        match stmt {
2046            GhostStatement::Fold(place) => {
2047                env.fold(&mut infcx.at(span), place)?;
2048            }
2049            GhostStatement::Unfold(place) => {
2050                env.unfold(infcx, place, span)?;
2051            }
2052            GhostStatement::Unblock(place) => env.unblock(infcx, place),
2053            GhostStatement::PtrToRef(place) => {
2054                env.ptr_to_ref_at_place(&mut infcx.at(span), place)?;
2055            }
2056        }
2057        dbg::statement!("end", stmt, infcx, env, span, &self);
2058        Ok(())
2059    }
2060
2061    #[track_caller]
2062    fn marker_at_dominator(&self, bb: BasicBlock) -> &Marker {
2063        marker_at_dominator(self.body, &self.markers, bb)
2064    }
2065
2066    fn dominators(&self) -> &'ck Dominators<BasicBlock> {
2067        self.body.dominators()
2068    }
2069
2070    fn ghost_stmts(&self) -> &'ck GhostStatements {
2071        &self.inherited.ghost_stmts[&self.checker_id]
2072    }
2073
2074    fn refine_default<T: Refine>(&self, ty: &T) -> QueryResult<T::Output> {
2075        ty.refine(&self.default_refiner)
2076    }
2077
2078    fn refine_with_holes<T: Refine>(&self, ty: &T) -> QueryResult<<T as Refine>::Output> {
2079        ty.refine(&Refiner::with_holes(self.genv, self.checker_id.root_id().to_def_id())?)
2080    }
2081}
2082
2083fn instantiate_args_for_fun_call(
2084    genv: GlobalEnv,
2085    caller_id: DefId,
2086    callee_id: DefId,
2087    args: &ty::GenericArgs,
2088) -> QueryResult<Vec<rty::GenericArg>> {
2089    let params_in_clauses = collect_params_in_clauses(genv, callee_id);
2090    let assumed_parametric_params = genv.assume_parametric_params(callee_id);
2091
2092    let hole_refiner = Refiner::new_for_item(genv, caller_id, |bty| {
2093        let sort = bty.sort();
2094        let bty = bty.shift_in_escaping(1);
2095        let constr = if !sort.is_unit() {
2096            rty::SubsetTy::new(bty, Expr::nu(), Expr::hole(rty::HoleKind::Pred))
2097        } else {
2098            rty::SubsetTy::trivial(bty, Expr::nu())
2099        };
2100        Binder::bind_with_sort(constr, sort)
2101    })?;
2102    let default_refiner = Refiner::default_for_item(genv, caller_id)?;
2103
2104    let callee_generics = genv.generics_of(callee_id)?;
2105    args.iter()
2106        .enumerate()
2107        .map(|(idx, arg)| {
2108            let param = callee_generics.param_at(idx, genv)?;
2109            let is_parametric = !params_in_clauses.contains(&idx)
2110                || assumed_parametric_params.contains(&(idx as u32));
2111            let refiner = if is_parametric { &hole_refiner } else { &default_refiner };
2112            refiner.refine_generic_arg(&param, arg)
2113        })
2114        .collect()
2115}
2116
2117fn instantiate_args_for_constructor(
2118    genv: GlobalEnv,
2119    caller_id: DefId,
2120    adt_id: DefId,
2121    args: &ty::GenericArgs,
2122) -> QueryResult<Vec<rty::GenericArg>> {
2123    let params_in_clauses = collect_params_in_clauses(genv, adt_id);
2124
2125    let adt_generics = genv.generics_of(adt_id)?;
2126    let hole_refiner = Refiner::with_holes(genv, caller_id)?;
2127    let default_refiner = Refiner::default_for_item(genv, caller_id)?;
2128    args.iter()
2129        .enumerate()
2130        .map(|(idx, arg)| {
2131            let param = adt_generics.param_at(idx, genv)?;
2132            let refiner =
2133                if params_in_clauses.contains(&idx) { &default_refiner } else { &hole_refiner };
2134            refiner.refine_generic_arg(&param, arg)
2135        })
2136        .collect()
2137}
2138
2139fn collect_params_in_clauses(genv: GlobalEnv, def_id: DefId) -> UnordSet<usize> {
2140    let tcx = genv.tcx();
2141    struct Collector {
2142        params: UnordSet<usize>,
2143    }
2144
2145    impl rustc_middle::ty::TypeVisitor<TyCtxt<'_>> for Collector {
2146        fn visit_ty(&mut self, t: rustc_middle::ty::Ty) {
2147            if let rustc_middle::ty::Param(param_ty) = t.kind() {
2148                self.params.insert(param_ty.index as usize);
2149            }
2150            t.super_visit_with(self);
2151        }
2152    }
2153    let mut vis = Collector { params: UnordSet::new() };
2154
2155    let span = genv.tcx().def_span(def_id);
2156    for (clause, _) in all_predicates_of(tcx, def_id) {
2157        if let Some(trait_pred) = clause.as_trait_clause() {
2158            let trait_id = trait_pred.def_id();
2159            let ignore = [
2160                LangItem::MetaSized,
2161                LangItem::Sized,
2162                LangItem::Tuple,
2163                LangItem::Copy,
2164                LangItem::Destruct,
2165            ];
2166            if ignore
2167                .iter()
2168                .any(|lang_item| tcx.require_lang_item(*lang_item, span) == trait_id)
2169            {
2170                continue;
2171            }
2172
2173            if tcx.fn_trait_kind_from_def_id(trait_id).is_some() {
2174                continue;
2175            }
2176            if tcx.get_diagnostic_item(sym::Hash) == Some(trait_id) {
2177                continue;
2178            }
2179            if tcx.get_diagnostic_item(sym::Eq) == Some(trait_id) {
2180                continue;
2181            }
2182        }
2183        if let Some(proj_pred) = clause.as_projection_clause() {
2184            let assoc_id = proj_pred.item_def_id();
2185            if genv.is_fn_output(assoc_id) {
2186                continue;
2187            }
2188        }
2189        if let Some(outlives_pred) = clause.as_type_outlives_clause() {
2190            // We skip outlives bounds if they are not 'static. A 'static bound means the type
2191            // implements `Any` which makes it unsound to instantiate the argument with refinements.
2192            if outlives_pred.skip_binder().1 != tcx.lifetimes.re_static {
2193                continue;
2194            }
2195        }
2196        clause.visit_with(&mut vis);
2197    }
2198    vis.params
2199}
2200
2201fn all_predicates_of(
2202    tcx: TyCtxt<'_>,
2203    id: DefId,
2204) -> impl Iterator<Item = &(rustc_middle::ty::Clause<'_>, Span)> {
2205    let mut next_id = Some(id);
2206    iter::from_fn(move || {
2207        next_id.take().map(|id| {
2208            let preds = tcx.predicates_of(id);
2209            next_id = preds.parent;
2210            preds.predicates.iter()
2211        })
2212    })
2213    .flatten()
2214}
2215
2216struct SkipConstr;
2217
2218impl TypeFolder for SkipConstr {
2219    fn fold_ty(&mut self, ty: &rty::Ty) -> rty::Ty {
2220        if let rty::TyKind::Constr(_, inner_ty) = ty.kind() {
2221            inner_ty.fold_with(self)
2222        } else {
2223            ty.super_fold_with(self)
2224        }
2225    }
2226}
2227
2228fn is_indexed_mut_skipping_constr(ty: &Ty) -> bool {
2229    let ty = SkipConstr.fold_ty(ty);
2230    if let rty::Ref!(_, inner_ty, Mutability::Mut) = ty.kind()
2231        && let TyKind::Indexed(..) = inner_ty.kind()
2232    {
2233        true
2234    } else {
2235        false
2236    }
2237}
2238
2239/// HACK(nilehmann) This let us infer parameters under mutable references for the simple case
2240/// where the formal argument is of the form `&mut B[@n]`, e.g., the type of the first argument
2241/// to `RVec::get_mut` is `&mut RVec<T>[@n]`. We should remove this after we implement opening of
2242/// mutable references.
2243fn infer_under_mut_ref_hack(rcx: &mut InferCtxt, actuals: &[Ty], fn_sig: &PolyFnSig) -> Vec<Ty> {
2244    iter::zip(actuals, fn_sig.skip_binder_ref().inputs())
2245        .map(|(actual, formal)| {
2246            if let rty::Ref!(re, deref_ty, Mutability::Mut) = actual.kind()
2247                && is_indexed_mut_skipping_constr(formal)
2248            {
2249                rty::Ty::mk_ref(*re, rcx.unpack(deref_ty), Mutability::Mut)
2250            } else {
2251                actual.clone()
2252            }
2253        })
2254        .collect()
2255}
2256
2257impl Mode for ShapeMode {
2258    const NAME: &str = "shape";
2259
2260    fn enter_basic_block<'ck, 'genv, 'tcx>(
2261        ck: &mut Checker<'ck, 'genv, 'tcx, ShapeMode>,
2262        _infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2263        bb: BasicBlock,
2264    ) -> TypeEnv<'ck> {
2265        ck.inherited.mode.bb_envs[&ck.checker_id][&bb].enter(&ck.body.local_decls)
2266    }
2267
2268    fn check_goto_join_point<'genv, 'tcx>(
2269        ck: &mut Checker<'_, 'genv, 'tcx, ShapeMode>,
2270        _: InferCtxt<'_, 'genv, 'tcx>,
2271        env: TypeEnv,
2272        span: Span,
2273        target: BasicBlock,
2274    ) -> Result<bool> {
2275        let bb_envs = &mut ck.inherited.mode.bb_envs;
2276        let target_bb_env = bb_envs.entry(ck.checker_id).or_default().get(&target);
2277        dbg::shape_goto_enter!(target, env, target_bb_env);
2278
2279        let modified = match bb_envs.entry(ck.checker_id).or_default().entry(target) {
2280            Entry::Occupied(mut entry) => entry.get_mut().join(env, span),
2281            Entry::Vacant(entry) => {
2282                let scope = marker_at_dominator(ck.body, &ck.markers, target)
2283                    .scope()
2284                    .unwrap_or_else(|| tracked_span_bug!());
2285                entry.insert(env.into_infer(scope));
2286                true
2287            }
2288        };
2289
2290        dbg::shape_goto_exit!(target, bb_envs[&ck.checker_id].get(&target));
2291        Ok(modified)
2292    }
2293
2294    fn clear(ck: &mut Checker<ShapeMode>, root: BasicBlock) {
2295        ck.visited.remove(root);
2296        for bb in ck.body.basic_blocks.indices() {
2297            if bb != root && ck.dominators().dominates(root, bb) {
2298                ck.inherited
2299                    .mode
2300                    .bb_envs
2301                    .entry(ck.checker_id)
2302                    .or_default()
2303                    .remove(&bb);
2304                ck.visited.remove(bb);
2305            }
2306        }
2307    }
2308}
2309
2310impl Mode for RefineMode {
2311    const NAME: &str = "refine";
2312
2313    fn enter_basic_block<'ck, 'genv, 'tcx>(
2314        ck: &mut Checker<'ck, 'genv, 'tcx, RefineMode>,
2315        infcx: &mut InferCtxt<'_, 'genv, 'tcx>,
2316        bb: BasicBlock,
2317    ) -> TypeEnv<'ck> {
2318        ck.inherited.mode.bb_envs[&ck.checker_id][&bb].enter(infcx, &ck.body.local_decls)
2319    }
2320
2321    fn check_goto_join_point(
2322        ck: &mut Checker<RefineMode>,
2323        mut infcx: InferCtxt,
2324        env: TypeEnv,
2325        terminator_span: Span,
2326        target: BasicBlock,
2327    ) -> Result<bool> {
2328        let bb_env = &ck.inherited.mode.bb_envs[&ck.checker_id][&target];
2329        tracked_span_dbg_assert_eq!(
2330            &ck.marker_at_dominator(target)
2331                .scope()
2332                .unwrap_or_else(|| tracked_span_bug!()),
2333            bb_env.scope()
2334        );
2335
2336        dbg::refine_goto!(target, infcx, env, bb_env);
2337
2338        env.check_goto(&mut infcx.at(terminator_span), bb_env, target)
2339            .with_span(terminator_span)?;
2340
2341        Ok(!ck.visited.contains(target))
2342    }
2343
2344    fn clear(_ck: &mut Checker<RefineMode>, _bb: BasicBlock) {
2345        bug!();
2346    }
2347}
2348
2349fn bool_int_cast(b: &Expr, int_ty: IntTy) -> Ty {
2350    let idx = Expr::ite(b, 1, 0);
2351    Ty::indexed(BaseTy::Int(int_ty), idx)
2352}
2353
2354/// Unlike [`char_uint_cast`] rust only allows `u8` to `char` casts, which are
2355/// non-lossy, so we can use indexed type directly.
2356fn uint_char_cast(idx: &Expr) -> Ty {
2357    let idx = Expr::cast(rty::Sort::Int, rty::Sort::Char, idx.clone());
2358    Ty::indexed(BaseTy::Char, idx)
2359}
2360
2361fn char_uint_cast(idx: &Expr, uint_ty: UintTy) -> Ty {
2362    let idx = Expr::cast(rty::Sort::Char, rty::Sort::Int, idx.clone());
2363    if uint_bit_width(uint_ty) >= 32 {
2364        // non-lossy cast: uint[cast(idx)]
2365        Ty::indexed(BaseTy::Uint(uint_ty), idx)
2366    } else {
2367        // lossy-cast: uint{v: cast(idx) <= max_value => v == cast(idx) }
2368        guarded_uint_ty(&idx, uint_ty)
2369    }
2370}
2371
2372fn bool_uint_cast(b: &Expr, uint_ty: UintTy) -> Ty {
2373    let idx = Expr::ite(b, 1, 0);
2374    Ty::indexed(BaseTy::Uint(uint_ty), idx)
2375}
2376
2377fn int_int_cast(idx: &Expr, int_ty1: IntTy, int_ty2: IntTy) -> Ty {
2378    if int_bit_width(int_ty1) <= int_bit_width(int_ty2) {
2379        Ty::indexed(BaseTy::Int(int_ty2), idx.clone())
2380    } else {
2381        Ty::int(int_ty2)
2382    }
2383}
2384
2385fn uint_int_cast(idx: &Expr, uint_ty: UintTy, int_ty: IntTy) -> Ty {
2386    if uint_bit_width(uint_ty) < int_bit_width(int_ty) {
2387        Ty::indexed(BaseTy::Int(int_ty), idx.clone())
2388    } else {
2389        Ty::int(int_ty)
2390    }
2391}
2392
2393fn int_uint_cast(idx: &Expr, int_ty: IntTy, uint_ty: UintTy) -> Ty {
2394    let non_neg = Expr::ge(idx.clone(), Expr::zero());
2395
2396    let guard: Expr = if int_bit_width(int_ty) <= uint_bit_width(uint_ty) {
2397        non_neg
2398    } else {
2399        // Cast is still possible if the value is known to fit.
2400        let fits = Expr::le(idx.clone(), Expr::uint_max(uint_ty));
2401        Expr::and(non_neg, fits)
2402    };
2403
2404    let eq = Expr::eq(Expr::nu(), idx.clone());
2405    Ty::exists_with_constr(BaseTy::Uint(uint_ty), Expr::implies(guard, eq))
2406}
2407
2408fn guarded_uint_ty(idx: &Expr, uint_ty: UintTy) -> Ty {
2409    // uint_ty2{v: idx <= max_value => v == idx }
2410    let max_value = Expr::uint_max(uint_ty);
2411    let guard = Expr::le(idx.clone(), max_value);
2412    let eq = Expr::eq(Expr::nu(), idx.clone());
2413    Ty::exists_with_constr(BaseTy::Uint(uint_ty), Expr::implies(guard, eq))
2414}
2415
2416fn uint_uint_cast(idx: &Expr, uint_ty1: UintTy, uint_ty2: UintTy) -> Ty {
2417    if uint_bit_width(uint_ty1) <= uint_bit_width(uint_ty2) {
2418        Ty::indexed(BaseTy::Uint(uint_ty2), idx.clone())
2419    } else {
2420        guarded_uint_ty(idx, uint_ty2)
2421    }
2422}
2423
2424fn uint_bit_width(uint_ty: UintTy) -> u64 {
2425    uint_ty
2426        .bit_width()
2427        .unwrap_or(config::pointer_width().bits())
2428}
2429
2430fn int_bit_width(int_ty: IntTy) -> u64 {
2431    int_ty.bit_width().unwrap_or(config::pointer_width().bits())
2432}
2433
2434impl ShapeResult {
2435    fn into_bb_envs(
2436        self,
2437        infcx: &mut InferCtxtRoot,
2438        body: &Body,
2439    ) -> FxHashMap<CheckerId, FxHashMap<BasicBlock, BasicBlockEnv>> {
2440        self.0
2441            .into_iter()
2442            .map(|(checker_id, shapes)| {
2443                let bb_envs = shapes
2444                    .into_iter()
2445                    .map(|(bb, shape)| (bb, shape.into_bb_env(infcx, body)))
2446                    .collect();
2447                (checker_id, bb_envs)
2448            })
2449            .collect()
2450    }
2451}
2452
2453fn marker_at_dominator<'a>(
2454    body: &Body,
2455    markers: &'a IndexVec<BasicBlock, Option<Marker>>,
2456    bb: BasicBlock,
2457) -> &'a Marker {
2458    let dominator = body
2459        .dominators()
2460        .immediate_dominator(bb)
2461        .unwrap_or_else(|| tracked_span_bug!());
2462    markers[dominator]
2463        .as_ref()
2464        .unwrap_or_else(|| tracked_span_bug!())
2465}
2466
2467pub(crate) mod errors {
2468    use flux_errors::{E0999, ErrorGuaranteed};
2469    use flux_infer::infer::InferErr;
2470    use flux_middle::{global_env::GlobalEnv, queries::ErrCtxt};
2471    use rustc_errors::Diagnostic;
2472    use rustc_hir::def_id::LocalDefId;
2473    use rustc_span::Span;
2474
2475    use crate::fluent_generated as fluent;
2476
2477    #[derive(Debug)]
2478    pub struct CheckerError {
2479        kind: InferErr,
2480        span: Span,
2481    }
2482
2483    impl CheckerError {
2484        pub fn emit(self, genv: GlobalEnv, fn_def_id: LocalDefId) -> ErrorGuaranteed {
2485            let dcx = genv.sess().dcx().handle();
2486            match self.kind {
2487                InferErr::UnsolvedEvar(_) => {
2488                    let mut diag =
2489                        dcx.struct_span_err(self.span, fluent::refineck_param_inference_error);
2490                    diag.code(E0999);
2491                    diag.emit()
2492                }
2493                InferErr::Query(err) => {
2494                    let level = rustc_errors::Level::Error;
2495                    err.at(ErrCtxt::FnCheck(self.span, fn_def_id))
2496                        .into_diag(dcx, level)
2497                        .emit()
2498                }
2499            }
2500        }
2501    }
2502
2503    pub trait ResultExt<T> {
2504        fn with_span(self, span: Span) -> Result<T, CheckerError>;
2505    }
2506
2507    impl<T, E> ResultExt<T> for Result<T, E>
2508    where
2509        E: Into<InferErr>,
2510    {
2511        fn with_span(self, span: Span) -> Result<T, CheckerError> {
2512            self.map_err(|err| CheckerError { kind: err.into(), span })
2513        }
2514    }
2515}