Skip to main content

flux_refineck/
checker.rs

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