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