Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs')
| -rw-r--r-- | crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs | 1705 |
1 files changed, 1705 insertions, 0 deletions
diff --git a/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs b/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs new file mode 100644 index 0000000000..099fa18168 --- /dev/null +++ b/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs @@ -0,0 +1,1705 @@ +//! A different sort of visitor for walking fn bodies. Unlike the +//! normal visitor, which just walks the entire body in one shot, the +//! `ExprUseVisitor` determines how expressions are being used. +//! +//! This is only used for upvar inference. + +use either::Either; +use hir_def::{ + AdtId, HasModule, VariantId, + attrs::AttrFlags, + hir::{ + Array, AsmOperand, BindingId, Expr, ExprId, ExprOrPatId, MatchArm, Pat, PatId, + RecordLitField, RecordSpread, Statement, + }, + resolver::ValueNs, +}; +use rustc_ast_ir::{try_visit, visit::VisitorResult}; +use rustc_type_ir::{ + FallibleTypeFolder, TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor, + inherent::{AdtDef, IntoKind, Ty as _}, +}; +use smallvec::{SmallVec, smallvec}; +use syntax::ast::{BinaryOp, UnaryOp}; +use tracing::{debug, instrument}; + +use crate::{ + Adjust, Adjustment, AutoBorrow, BindingMode, + infer::{CaptureSourceStack, InferenceContext, UpvarCapture, closure::analysis::BorrowKind}, + method_resolution::CandidateId, + next_solver::{DbInterner, ErrorGuaranteed, StoredTy, Ty, TyKind}, + upvars::UpvarsRef, + utils::EnumerateAndAdjustIterator, +}; + +type Result<T = (), E = ErrorGuaranteed> = std::result::Result<T, E>; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ProjectionKind { + /// A dereference of a pointer, reference or `Box<T>` of the given type. + Deref, + + /// `B.F` where `B` is the base expression and `F` is + /// the field. The field is identified by which variant + /// it appears in along with a field index. The variant + /// is used for enums. + Field { field_idx: u32, variant_idx: u32 }, + + /// Some index like `B[x]`, where `B` is the base + /// expression. We don't preserve the index `x` because + /// we won't need it. + Index, + + /// A subslice covering a range of values like `B[x..y]`. + Subslice, + + /// `unwrap_binder!(expr)` + UnwrapUnsafeBinder, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum PlaceBase { + /// A temporary variable. + Rvalue, + /// A named `static` item. + StaticItem, + /// A named local variable. + Local(BindingId), + /// An upvar referenced by closure env. + Upvar { closure: ExprId, var_id: BindingId }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Projection { + /// Type after the projection is applied. + pub ty: StoredTy, + + /// Defines the kind of access made by the projection. + pub kind: ProjectionKind, +} + +/// A `Place` represents how a value is located in memory. This does not +/// always correspond to a syntactic place expression. For example, when +/// processing a pattern, a `Place` can be used to refer to the sub-value +/// currently being inspected. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Place { + /// The type of the `PlaceBase` + pub base_ty: StoredTy, + /// The "outermost" place that holds this value. + pub base: PlaceBase, + /// How this place is derived from the base place. + pub projections: Vec<Projection>, +} + +impl<'db> TypeVisitable<DbInterner<'db>> for Place { + fn visit_with<V: TypeVisitor<DbInterner<'db>>>(&self, visitor: &mut V) -> V::Result { + let Self { base_ty, base: _, projections } = self; + try_visit!(base_ty.as_ref().visit_with(visitor)); + for proj in projections { + let Projection { ty, kind: _ } = proj; + try_visit!(ty.as_ref().visit_with(visitor)); + } + V::Result::output() + } +} + +impl<'db> TypeFoldable<DbInterner<'db>> for Place { + fn try_fold_with<F: FallibleTypeFolder<DbInterner<'db>>>( + self, + folder: &mut F, + ) -> Result<Self, F::Error> { + let Self { base_ty, base, projections } = self; + let base_ty = base_ty.as_ref().try_fold_with(folder)?.store(); + let projections = projections + .into_iter() + .map(|proj| { + let Projection { ty, kind } = proj; + let ty = ty.as_ref().try_fold_with(folder)?.store(); + Ok(Projection { ty, kind }) + }) + .collect::<Result<_, _>>()?; + Ok(Self { base_ty, base, projections }) + } + + fn fold_with<F: TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self { + let Self { base_ty, base, projections } = self; + let base_ty = base_ty.as_ref().fold_with(folder).store(); + let projections = projections + .into_iter() + .map(|proj| { + let Projection { ty, kind } = proj; + let ty = ty.as_ref().fold_with(folder).store(); + Projection { ty, kind } + }) + .collect(); + Self { base_ty, base, projections } + } +} + +impl Place { + /// Returns an iterator of the types that have to be dereferenced to access + /// the `Place`. + /// + /// The types are in the reverse order that they are applied. So if + /// `x: &*const u32` and the `Place` is `**x`, then the types returned are + ///`*const u32` then `&*const u32`. + pub fn deref_tys<'db>(&self) -> impl Iterator<Item = Ty<'db>> { + self.projections.iter().enumerate().rev().filter_map(move |(index, proj)| { + if ProjectionKind::Deref == proj.kind { + Some(self.ty_before_projection(index)) + } else { + None + } + }) + } + + /// Returns the type of this `Place` after all projections have been applied. + pub fn ty<'db>(&self) -> Ty<'db> { + self.projections.last().map_or(self.base_ty.as_ref(), |proj| proj.ty.as_ref()) + } + + /// Returns the type of this `Place` immediately before `projection_index`th projection + /// is applied. + pub fn ty_before_projection<'db>(&self, projection_index: usize) -> Ty<'db> { + assert!(projection_index < self.projections.len()); + if projection_index == 0 { + self.base_ty.as_ref() + } else { + self.projections[projection_index - 1].ty.as_ref() + } + } +} + +/// A `PlaceWithOrigin` represents how a value is located in memory. This does not +/// always correspond to a syntactic place expression. For example, when +/// processing a pattern, a `Place` can be used to refer to the sub-value +/// currently being inspected. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct PlaceWithOrigin { + /// `ExprId`s or `PatId`s of the expressions or patterns producing this value. + pub origins: SmallVec<[CaptureSourceStack; 2]>, + + /// Information about the `Place`. + pub place: Place, +} + +impl PlaceWithOrigin { + fn new_no_projections<'db>( + origin: impl Into<ExprOrPatId>, + base_ty: Ty<'db>, + base: PlaceBase, + ) -> PlaceWithOrigin { + Self::new( + smallvec![CaptureSourceStack::from_single(origin.into())], + base_ty, + base, + Vec::new(), + ) + } + + fn new<'db>( + origins: SmallVec<[CaptureSourceStack; 2]>, + base_ty: Ty<'db>, + base: PlaceBase, + projections: Vec<Projection>, + ) -> PlaceWithOrigin { + debug_assert!(origins.iter().all(|origin| origin.len() == projections.len() + 1)); + PlaceWithOrigin { origins, place: Place { base_ty: base_ty.store(), base, projections } } + } + + fn push_projection(&mut self, projection: Projection, origin: ExprOrPatId) { + self.place.projections.push(projection); + for origin_stack in &mut self.origins { + origin_stack.push(origin); + } + } +} + +/// The `FakeReadCause` describes the type of pattern why a FakeRead statement exists. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum FakeReadCause { + /// A fake read injected into a match guard to ensure that the discriminants + /// that are being matched on aren't modified while the match guard is being + /// evaluated. + /// + /// At the beginning of each match guard, a fake borrow is + /// inserted for each discriminant accessed in the entire `match` statement. + /// + /// Then, at the end of the match guard, a `FakeRead(ForMatchGuard)` is + /// inserted to keep the fake borrows alive until that point. + /// + /// This should ensure that you cannot change the variant for an enum while + /// you are in the midst of matching on it. + ForMatchGuard, + + /// Fake read of the scrutinee of a `match` or destructuring `let` + /// (i.e. `let` with non-trivial pattern). + /// + /// In `match x { ... }`, we generate a `FakeRead(ForMatchedPlace, x)` + /// and insert it into the `otherwise_block` (which is supposed to be + /// unreachable for irrefutable pattern-matches like `match` or `let`). + /// + /// This is necessary because `let x: !; match x {}` doesn't generate any + /// actual read of x, so we need to generate a `FakeRead` to check that it + /// is initialized. + /// + /// If the `FakeRead(ForMatchedPlace)` is being performed with a closure + /// that doesn't capture the required upvars, the `FakeRead` within the + /// closure is omitted entirely. + /// + /// To make sure that this is still sound, if a closure matches against + /// a Place starting with an Upvar, we hoist the `FakeRead` to the + /// definition point of the closure. + /// + /// If the `FakeRead` comes from being hoisted out of a closure like this, + /// we record the `ExprId` of the closure. Otherwise, the `Option` will be `None`. + // + // We can use LocalDefId here since fake read statements are removed + // before codegen in the `CleanupNonCodegenStatements` pass. + ForMatchedPlace(Option<ExprId>), + + /// A fake read injected into a match guard to ensure that the places + /// bound by the pattern are immutable for the duration of the match guard. + /// + /// Within a match guard, references are created for each place that the + /// pattern creates a binding for — this is known as the `RefWithinGuard` + /// version of the variables. To make sure that the references stay + /// alive until the end of the match guard, and properly prevent the + /// places in question from being modified, a `FakeRead(ForGuardBinding)` + /// is inserted at the end of the match guard. + /// + /// For details on how these references are created, see the extensive + /// documentation on `bind_matched_candidate_for_guard` in + /// `rustc_mir_build`. + ForGuardBinding, + + /// Officially, the semantics of + /// + /// `let pattern = <expr>;` + /// + /// is that `<expr>` is evaluated into a temporary and then this temporary is + /// into the pattern. + /// + /// However, if we see the simple pattern `let var = <expr>`, we optimize this to + /// evaluate `<expr>` directly into the variable `var`. This is mostly unobservable, + /// but in some cases it can affect the borrow checker, as in #53695. + /// + /// Therefore, we insert a `FakeRead(ForLet)` immediately after each `let` + /// with a trivial pattern. + /// + /// FIXME: `ExprUseVisitor` has an entirely different opinion on what `FakeRead(ForLet)` + /// is supposed to mean. If it was accurate to what MIR lowering does, + /// would it even make sense to hoist these out of closures like + /// `ForMatchedPlace`? + ForLet(Option<ExprId>), + + /// Currently, index expressions overloaded through the `Index` trait + /// get lowered differently than index expressions with builtin semantics + /// for arrays and slices — the latter will emit code to perform + /// bound checks, and then return a MIR place that will only perform the + /// indexing "for real" when it gets incorporated into an instruction. + /// + /// This is observable in the fact that the following compiles: + /// + /// ``` + /// fn f(x: &mut [&mut [u32]], i: usize) { + /// x[i][x[i].len() - 1] += 1; + /// } + /// ``` + /// + /// However, we need to be careful to not let the user invalidate the + /// bound check with an expression like + /// + /// `(*x)[1][{ x = y; 4}]` + /// + /// Here, the first bounds check would be invalidated when we evaluate the + /// second index expression. To make sure that this doesn't happen, we + /// create a fake borrow of `x` and hold it while we evaluate the second + /// index. + /// + /// This borrow is kept alive by a `FakeRead(ForIndex)` at the end of its + /// scope. + ForIndex, +} + +/// This trait defines the callbacks you can expect to receive when +/// employing the ExprUseVisitor. +pub(crate) trait Delegate<'db> { + /// The value found at `place` is moved, depending + /// on `mode`. Where `diag_expr_id` is the id used for diagnostics for `place`. + /// + /// If the value is `Copy`, [`copy`][Self::copy] is called instead, which + /// by default falls back to [`borrow`][Self::borrow]. + /// + /// The parameter `diag_expr_id` indicates the HIR id that ought to be used for + /// diagnostics. Around pattern matching such as `let pat = expr`, the diagnostic + /// id will be the id of the expression `expr` but the place itself will have + /// the id of the binding in the pattern `pat`. + fn consume(&mut self, place_with_id: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>); + + /// The value found at `place` is used, depending + /// on `mode`. Where `diag_expr_id` is the id used for diagnostics for `place`. + /// + /// Use of a `Copy` type in a ByUse context is considered a use + /// by `ImmBorrow` and `borrow` is called instead. This is because + /// a shared borrow is the "minimum access" that would be needed + /// to perform a copy. + /// + /// + /// The parameter `diag_expr_id` indicates the HIR id that ought to be used for + /// diagnostics. Around pattern matching such as `let pat = expr`, the diagnostic + /// id will be the id of the expression `expr` but the place itself will have + /// the id of the binding in the pattern `pat`. + fn use_cloned(&mut self, place_with_id: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>); + + /// The value found at `place` is being borrowed with kind `bk`. + /// `diag_expr_id` is the id used for diagnostics (see `consume` for more details). + fn borrow( + &mut self, + place_with_id: PlaceWithOrigin, + bk: BorrowKind, + ctx: &mut InferenceContext<'_, 'db>, + ); + + /// The value found at `place` is being copied. + /// `diag_expr_id` is the id used for diagnostics (see `consume` for more details). + /// + /// If an implementation is not provided, use of a `Copy` type in a ByValue context is instead + /// considered a use by `ImmBorrow` and `borrow` is called instead. This is because a shared + /// borrow is the "minimum access" that would be needed to perform a copy. + fn copy(&mut self, place_with_id: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + // In most cases, copying data from `x` is equivalent to doing `*&x`, so by default + // we treat a copy of `x` as a borrow of `x`. + self.borrow(place_with_id, BorrowKind::Immutable, ctx) + } + + /// The path at `assignee_place` is being assigned to. + /// `diag_expr_id` is the id used for diagnostics (see `consume` for more details). + fn mutate(&mut self, assignee_place: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>); + + /// The path at `binding_place` is a binding that is being initialized. + /// + /// This covers cases such as `let x = 42;` + fn bind(&mut self, binding_place: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + // Bindings can normally be treated as a regular assignment, so by default we + // forward this to the mutate callback. + self.mutate(binding_place, ctx) + } + + /// The `place` should be a fake read because of specified `cause`. + fn fake_read( + &mut self, + place_with_id: PlaceWithOrigin, + cause: FakeReadCause, + ctx: &mut InferenceContext<'_, 'db>, + ); +} + +impl<'db, D: Delegate<'db>> Delegate<'db> for &mut D { + fn consume(&mut self, place_with_id: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + (**self).consume(place_with_id, ctx) + } + + fn use_cloned(&mut self, place_with_id: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + (**self).use_cloned(place_with_id, ctx) + } + + fn borrow( + &mut self, + place_with_id: PlaceWithOrigin, + bk: BorrowKind, + ctx: &mut InferenceContext<'_, 'db>, + ) { + (**self).borrow(place_with_id, bk, ctx) + } + + fn copy(&mut self, place_with_id: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + (**self).copy(place_with_id, ctx) + } + + fn mutate(&mut self, assignee_place: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + (**self).mutate(assignee_place, ctx) + } + + fn bind(&mut self, binding_place: PlaceWithOrigin, ctx: &mut InferenceContext<'_, 'db>) { + (**self).bind(binding_place, ctx) + } + + fn fake_read( + &mut self, + place_with_id: PlaceWithOrigin, + cause: FakeReadCause, + ctx: &mut InferenceContext<'_, 'db>, + ) { + (**self).fake_read(place_with_id, cause, ctx) + } +} + +/// A visitor that reports how each expression is being used. +/// +/// See [module-level docs][self] and [`Delegate`] for details. +pub(crate) struct ExprUseVisitor<'a, 'b, 'db, D: Delegate<'db>> { + cx: &'a mut InferenceContext<'b, 'db>, + delegate: D, + closure_expr: ExprId, + upvars: UpvarsRef<'db>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PatWalkMode { + /// `let`, `match`. + Declaration, + /// Destructuring assignment. + Assignment, +} + +impl<'a, 'b, 'db, D: Delegate<'db>> ExprUseVisitor<'a, 'b, 'db, D> { + /// Creates the ExprUseVisitor, configuring it with the various options provided: + /// + /// - `delegate` -- who receives the callbacks + /// - `param_env` --- parameter environment for trait lookups (esp. pertaining to `Copy`) + /// - `typeck_results` --- typeck results for the code being analyzed + pub(crate) fn new( + cx: &'a mut InferenceContext<'b, 'db>, + closure_expr: ExprId, + upvars: UpvarsRef<'db>, + delegate: D, + ) -> Self { + ExprUseVisitor { delegate, closure_expr, upvars, cx } + } + + pub(crate) fn consume_closure_body(&mut self, params: &[PatId], body: ExprId) -> Result { + for ¶m in params { + let param_ty = self.pat_ty_adjusted(param)?; + debug!("consume_body: param_ty = {:?}", param_ty); + + let param_place = self.cat_rvalue(param.into(), param_ty); + + self.fake_read_scrutinee(param_place.clone(), false); + self.walk_pat(param_place, param, false, PatWalkMode::Declaration)?; + } + + self.consume_expr(body)?; + + Ok(()) + } + + #[instrument(skip(self), level = "debug")] + fn consume_or_copy(&mut self, place_with_id: PlaceWithOrigin) { + if self.cx.table.type_is_copy_modulo_regions(place_with_id.place.ty()) { + self.delegate.copy(place_with_id, self.cx); + } else { + self.delegate.consume(place_with_id, self.cx); + } + } + + #[instrument(skip(self), level = "debug")] + pub(crate) fn consume_clone_or_copy(&mut self, place_with_id: PlaceWithOrigin) { + // `x.use` will do one of the following + // * if it implements `Copy`, it will be a copy + // * if it implements `UseCloned`, it will be a call to `clone` + // * otherwise, it is a move + // + // we do a conservative approximation of this, treating it as a move unless we know that it implements copy or `UseCloned` + if self.cx.table.type_is_copy_modulo_regions(place_with_id.place.ty()) { + self.delegate.copy(place_with_id, self.cx); + } else if self.cx.table.type_is_use_cloned_modulo_regions(place_with_id.place.ty()) { + self.delegate.use_cloned(place_with_id, self.cx); + } else { + self.delegate.consume(place_with_id, self.cx); + } + } + + fn consume_exprs(&mut self, exprs: &[ExprId]) -> Result { + for &expr in exprs { + self.consume_expr(expr)?; + } + Ok(()) + } + + // FIXME: It's suspicious that this is public; clippy should probably use `walk_expr`. + #[instrument(skip(self), level = "debug")] + pub(crate) fn consume_expr(&mut self, expr: ExprId) -> Result { + let place_with_id = self.cat_expr(expr)?; + self.consume_or_copy(place_with_id); + self.walk_expr(expr)?; + Ok(()) + } + + fn mutate_expr(&mut self, expr: ExprId) -> Result { + let place_with_id = self.cat_expr(expr)?; + self.delegate.mutate(place_with_id, self.cx); + self.walk_expr(expr)?; + Ok(()) + } + + #[instrument(skip(self), level = "debug")] + fn borrow_expr(&mut self, expr: ExprId, bk: BorrowKind) -> Result { + let place_with_id = self.cat_expr(expr)?; + self.delegate.borrow(place_with_id, bk, self.cx); + self.walk_expr(expr)?; + Ok(()) + } + + #[instrument(skip(self), level = "debug")] + pub(crate) fn walk_expr(&mut self, expr: ExprId) -> Result { + self.walk_adjustment(expr)?; + + match self.cx.store[expr] { + Expr::Path(_) => {} + + Expr::UnaryOp { op: UnaryOp::Deref, expr: base } => { + // *base + self.walk_expr(base)?; + } + + Expr::Field { expr: base, .. } => { + // base.f + self.walk_expr(base)?; + } + + Expr::Index { base: lhs, index: rhs } => { + // lhs[rhs] + self.walk_expr(lhs)?; + self.consume_expr(rhs)?; + } + + Expr::Call { callee, ref args } => { + // callee(args) + self.consume_expr(callee)?; + self.consume_exprs(args)?; + } + + Expr::MethodCall { receiver, ref args, .. } => { + // callee.m(args) + self.consume_expr(receiver)?; + self.consume_exprs(args)?; + } + + Expr::RecordLit { ref fields, spread, .. } => { + self.walk_struct_expr(fields, spread)?; + } + + Expr::Tuple { ref exprs } => { + self.consume_exprs(exprs)?; + } + + Expr::If { + condition: cond_expr, + then_branch: then_expr, + else_branch: opt_else_expr, + } => { + self.consume_expr(cond_expr)?; + self.consume_expr(then_expr)?; + if let Some(else_expr) = opt_else_expr { + self.consume_expr(else_expr)?; + } + } + + Expr::Let { pat, expr: init } => { + self.walk_local(init, pat, None, |this| { + this.borrow_expr(init, BorrowKind::Immutable) + })?; + } + + Expr::Match { expr: discr, ref arms } => { + let discr_place = self.cat_expr(discr)?; + self.fake_read_scrutinee(discr_place.clone(), true); + self.walk_expr(discr)?; + + for arm in arms { + self.walk_arm(discr_place.clone(), arm)?; + } + } + + Expr::Array(Array::ElementList { elements: ref exprs }) => { + self.consume_exprs(exprs)?; + } + + Expr::Ref { expr: base, mutability: m, .. } => { + // &base + // make sure that the thing we are pointing out stays valid + // for the lifetime `scope_r` of the resulting ptr: + let bk = BorrowKind::from_hir_mutbl(m); + self.borrow_expr(base, bk)?; + } + + Expr::InlineAsm(ref asm) => { + for (_, op) in &asm.operands { + match *op { + AsmOperand::In { expr, .. } => { + self.consume_expr(expr)?; + } + AsmOperand::Out { expr: Some(expr), .. } + | AsmOperand::InOut { expr, .. } => { + self.mutate_expr(expr)?; + } + AsmOperand::SplitInOut { in_expr, out_expr, .. } => { + self.consume_expr(in_expr)?; + if let Some(out_expr) = out_expr { + self.mutate_expr(out_expr)?; + } + } + AsmOperand::Out { expr: None, .. } + | AsmOperand::Const { .. } + | AsmOperand::Sym { .. } => {} + AsmOperand::Label(block) => { + self.walk_expr(block)?; + } + } + } + } + + Expr::Continue { .. } + | Expr::Literal(..) + | Expr::Const(..) + | Expr::OffsetOf(..) + | Expr::Missing + | Expr::Underscore => {} + + Expr::Loop { body: blk, .. } => { + self.walk_expr(blk)?; + } + + Expr::UnaryOp { expr: lhs, .. } => { + self.consume_expr(lhs)?; + } + + Expr::BinaryOp { + lhs, + rhs, + op: Some(BinaryOp::ArithOp(..) | BinaryOp::CmpOp(..) | BinaryOp::LogicOp(..)), + } => { + self.consume_expr(lhs)?; + self.consume_expr(rhs)?; + } + + Expr::Block { ref statements, tail, .. } + | Expr::Unsafe { ref statements, tail, .. } => { + for stmt in statements { + self.walk_stmt(stmt)?; + } + + if let Some(tail_expr) = tail { + self.consume_expr(tail_expr)?; + } + } + + Expr::Break { expr: opt_expr, .. } | Expr::Return { expr: opt_expr } => { + if let Some(expr) = opt_expr { + self.consume_expr(expr)?; + } + } + + Expr::Become { expr } | Expr::Await { expr } | Expr::Box { expr } => { + self.consume_expr(expr)?; + } + + Expr::Assignment { target, value } => { + self.walk_expr(value)?; + let expr_place = self.cat_expr(value)?; + let update_guard = + self.cx.resolver.update_to_inner_scope(self.cx.db, self.cx.owner, expr); + self.walk_pat(expr_place, target, false, PatWalkMode::Assignment)?; + self.cx.resolver.reset_to_guard(update_guard); + } + + Expr::Cast { expr: base, .. } => { + self.consume_expr(base)?; + } + + Expr::BinaryOp { lhs, rhs, op: None | Some(BinaryOp::Assignment { .. }) } => { + self.consume_expr(lhs)?; + self.consume_expr(rhs)?; + } + + Expr::Array(Array::Repeat { initializer: base, .. }) => { + self.consume_expr(base)?; + } + + Expr::Closure { .. } => { + self.walk_captures(expr); + } + + Expr::Yield { expr: value } | Expr::Yeet { expr: value } => { + if let Some(value) = value { + self.consume_expr(value)?; + } + } + + Expr::Range { lhs, rhs, .. } => { + if let Some(lhs) = lhs { + self.consume_expr(lhs)?; + } + if let Some(rhs) = rhs { + self.consume_expr(rhs)?; + } + } + } + Ok(()) + } + + fn walk_stmt(&mut self, stmt: &Statement) -> Result { + match *stmt { + Statement::Let { pat, initializer: Some(expr), else_branch: els, .. } => { + self.walk_local(expr, pat, els, |_| Ok(()))?; + } + + Statement::Let { .. } => {} + + Statement::Item(_) => { + // We don't visit nested items in this visitor, + // only the fn body we were given. + } + + Statement::Expr { expr, .. } => { + self.consume_expr(expr)?; + } + } + Ok(()) + } + + #[instrument(skip(self), level = "debug")] + fn fake_read_scrutinee(&mut self, discr_place: PlaceWithOrigin, refutable: bool) { + let closure_def_id = match discr_place.place.base { + PlaceBase::Upvar { closure, var_id: _ } => Some(closure), + _ => None, + }; + + let cause = if refutable { + FakeReadCause::ForMatchedPlace(closure_def_id) + } else { + FakeReadCause::ForLet(closure_def_id) + }; + + self.delegate.fake_read(discr_place, cause, self.cx); + } + + fn walk_local<F>(&mut self, expr: ExprId, pat: PatId, els: Option<ExprId>, mut f: F) -> Result + where + F: FnMut(&mut Self) -> Result, + { + self.walk_expr(expr)?; + let expr_place = self.cat_expr(expr)?; + f(self)?; + self.fake_read_scrutinee(expr_place.clone(), els.is_some()); + self.walk_pat(expr_place, pat, false, PatWalkMode::Declaration)?; + if let Some(els) = els { + self.walk_expr(els)?; + } + Ok(()) + } + + fn walk_struct_expr(&mut self, fields: &[RecordLitField], spread: RecordSpread) -> Result { + // Consume the expressions supplying values for each field. + for field in fields { + self.consume_expr(field.expr)?; + } + + let RecordSpread::Expr(with_expr) = spread else { return Ok(()) }; + + let with_place = self.cat_expr(with_expr)?; + + // Select just those fields of the `with` + // expression that will actually be used + match self.cx.table.structurally_resolve_type(with_place.place.ty()).kind() { + TyKind::Adt(adt, args) if adt.is_struct() => { + let AdtId::StructId(adt) = adt.def_id().0 else { unreachable!() }; + let adt_fields = VariantId::from(adt).fields(self.cx.db).fields(); + let adt_field_types = self.cx.db.field_types(adt.into()); + // Consume those fields of the with expression that are needed. + for (f_index, with_field) in adt_fields.iter() { + let is_mentioned = fields.iter().any(|f| f.name == with_field.name); + if !is_mentioned { + let field_place = self.cat_projection( + with_expr.into(), + with_place.clone(), + adt_field_types[f_index].get().instantiate(self.cx.interner(), args), + ProjectionKind::Field { + field_idx: f_index.into_raw().into_u32(), + variant_idx: 0, + }, + ); + self.consume_or_copy(field_place); + } + } + } + _ => {} + } + + // walk the with expression so that complex expressions + // are properly handled. + self.walk_expr(with_expr)?; + + Ok(()) + } + + fn expr_adjustments(&self, expr: ExprId) -> SmallVec<[Adjustment; 5]> { + // Due to borrowck problems, we cannot borrow the adjustments, unfortunately. + self.cx.result.expr_adjustment(expr).unwrap_or_default().into() + } + + /// Invoke the appropriate delegate calls for anything that gets + /// consumed or borrowed as part of the automatic adjustment + /// process. + fn walk_adjustment(&mut self, expr: ExprId) -> Result { + let adjustments = self.expr_adjustments(expr); + let mut place_with_id = self.cat_expr_unadjusted(expr)?; + for adjustment in &adjustments { + debug!("walk_adjustment expr={:?} adj={:?}", expr, adjustment); + match adjustment.kind { + Adjust::NeverToAny | Adjust::Pointer(_) => { + // Creating a closure/fn-pointer or unsizing consumes + // the input and stores it into the resulting rvalue. + self.consume_or_copy(place_with_id.clone()); + } + + Adjust::Deref(None) => {} + + // Autoderefs for overloaded Deref calls in fact reference + // their receiver. That is, if we have `(*x)` where `x` + // is of type `Rc<T>`, then this in fact is equivalent to + // `x.deref()`. Since `deref()` is declared with `&self`, + // this is an autoref of `x`. + Adjust::Deref(Some(ref deref)) => { + let bk = BorrowKind::from_mutbl(deref.0); + self.delegate.borrow(place_with_id.clone(), bk, self.cx); + } + + Adjust::Borrow(ref autoref) => { + self.walk_autoref(expr, place_with_id.clone(), autoref); + } + } + place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?; + } + Ok(()) + } + + /// Walks the autoref `autoref` applied to the autoderef'd + /// `expr`. `base_place` is `expr` represented as a place, + /// after all relevant autoderefs have occurred. + fn walk_autoref(&mut self, expr: ExprId, base_place: PlaceWithOrigin, autoref: &AutoBorrow) { + debug!("walk_autoref(expr={:?} base_place={:?} autoref={:?})", expr, base_place, autoref); + + match *autoref { + AutoBorrow::Ref(m) => { + self.delegate.borrow(base_place, BorrowKind::from_mutbl(m.into()), self.cx); + } + + AutoBorrow::RawPtr(m) => { + debug!("walk_autoref: expr={:?} base_place={:?}", expr, base_place); + + self.delegate.borrow(base_place, BorrowKind::from_mutbl(m), self.cx); + } + } + } + + fn walk_arm(&mut self, discr_place: PlaceWithOrigin, arm: &MatchArm) -> Result { + self.walk_pat(discr_place, arm.pat, arm.guard.is_some(), PatWalkMode::Declaration)?; + + if let Some(e) = arm.guard { + self.consume_expr(e)?; + } + + self.consume_expr(arm.expr) + } + + /// The core driver for walking a pattern + /// + /// This should mirror how pattern-matching gets lowered to MIR, as + /// otherwise lowering will ICE when trying to resolve the upvars. + /// + /// However, it is okay to approximate it here by doing *more* accesses than + /// the actual MIR builder will, which is useful when some checks are too + /// cumbersome to perform here. For example, if after typeck it becomes + /// clear that only one variant of an enum is inhabited, and therefore a + /// read of the discriminant is not necessary, `walk_pat` will have + /// over-approximated the necessary upvar capture granularity. + /// + /// Do note that discrepancies like these do still create obscure corners + /// in the semantics of the language, and should be avoided if possible. + #[instrument(skip(self), level = "debug")] + fn walk_pat( + &mut self, + discr_place: PlaceWithOrigin, + pat: PatId, + has_guard: bool, + mode: PatWalkMode, + ) -> Result { + self.cat_pattern(discr_place.clone(), pat, &mut |this, place, pat| { + debug!("walk_pat: pat.kind={:?}", this.cx.store[pat]); + let read_discriminant = { + let place = place.clone(); + |this: &mut Self| { + this.delegate.borrow(place, BorrowKind::Immutable, this.cx); + } + }; + + match this.cx.store[pat] { + Pat::Bind { id, .. } => { + debug!("walk_pat: binding place={:?} pat={:?}", place, pat); + let bm = this.cx.result.binding_modes[pat]; + debug!("walk_pat: pat.hir_id={:?} bm={:?}", pat, bm); + + // pat_ty: the type of the binding being produced. + let pat_ty = this.node_ty(pat.into())?; + debug!("walk_pat: pat_ty={:?}", pat_ty); + + if let Ok(binding_place) = this.cat_local(pat.into(), pat_ty, id) { + this.delegate.bind(binding_place, this.cx); + } + + // Subtle: MIR desugaring introduces immutable borrows for each pattern + // binding when lowering pattern guards to ensure that the guard does not + // modify the scrutinee. + if has_guard { + read_discriminant(this); + } + + // It is also a borrow or copy/move of the value being matched. + // In a cases of pattern like `let pat = upvar`, don't use the span + // of the pattern, as this just looks confusing, instead use the span + // of the discriminant. + match this.cx.result.binding_mode(pat) { + Some(BindingMode::Ref(m)) => { + let bk = BorrowKind::from_mutbl(m); + this.delegate.borrow(place, bk, this.cx); + } + None | Some(BindingMode::Move) => { + debug!("walk_pat binding consuming pat"); + this.consume_or_copy(place); + } + } + } + Pat::Path(ref path) => { + // A `Path` pattern is just a name like `Foo`. This is either a + // named constant or else it refers to an ADT variant + + let is_assoc_const = this + .cx + .result + .assoc_resolutions_for_pat(pat) + .is_some_and(|it| matches!(it.0, CandidateId::ConstId(_))); + let resolution = this.cx.resolver.resolve_path_in_value_ns_fully( + this.cx.db, + path, + this.cx.store.pat_path_hygiene(pat), + ); + let is_normal_const = matches!(resolution, Some(ValueNs::ConstId(_))); + if mode == PatWalkMode::Assignment + && let Some(ValueNs::LocalBinding(local)) = resolution + { + let pat_ty = this.pat_ty(pat)?; + let place = this.cat_local(pat.into(), pat_ty, local)?; + this.delegate.mutate(place, this.cx); + } else if is_assoc_const || is_normal_const { + // Named constants have to be equated with the value + // being matched, so that's a read of the value being matched. + // + // FIXME: Does the MIR code skip this read when matching on a ZST? + // If so, we can also skip it here. + read_discriminant(this); + } else if this.is_multivariant_adt(place.place.ty()) { + // Otherwise, this is a struct/enum variant, and so it's + // only a read if we need to read the discriminant. + read_discriminant(this); + } + } + Pat::Lit(_) | Pat::ConstBlock(_) | Pat::Range { .. } => { + // When matching against a literal or range, we need to + // borrow the place to compare it against the pattern. + // + // Note that we do this read even if the range matches all + // possible values, such as 0..=u8::MAX. This is because + // we don't want to depend on consteval here. + // + // FIXME: What if the type being matched only has one + // possible value? + read_discriminant(this); + } + Pat::Record { .. } | Pat::TupleStruct { .. } => { + if this.is_multivariant_adt(place.place.ty()) { + read_discriminant(this); + } + } + Pat::Slice { prefix: ref lhs, slice: wild, suffix: ref rhs } => { + // We don't need to test the length if the pattern is `[..]` + if matches!((&**lhs, wild, &**rhs), (&[], Some(_), &[])) + // Arrays have a statically known size, so + // there is no need to read their length + || place.place.ty().strip_references().is_array() + { + // No read necessary + } else { + read_discriminant(this); + } + } + Pat::Expr(expr) if mode == PatWalkMode::Assignment => { + // Destructuring assignment. + this.mutate_expr(expr)?; + } + Pat::Or(_) + | Pat::Box { .. } + | Pat::Ref { .. } + | Pat::Tuple { .. } + | Pat::Wild + | Pat::Missing => { + // If the PatKind is Or, Box, Ref, Guard, or Tuple, the relevant accesses + // are made later as these patterns contains subpatterns. + // If the PatKind is Missing, Wild or Err, any relevant accesses are made when processing + // the other patterns that are part of the match + } + Pat::Expr(_) => {} + } + + Ok(()) + }) + } + + /// Handle the case where the current body contains a closure. + /// + /// When the current body being handled is a closure, then we must make sure that + /// - The parent closure only captures Places from the nested closure that are not local to it. + /// + /// In the following example the closures `c` only captures `p.x` even though `incr` + /// is a capture of the nested closure + /// + /// ``` + /// struct P { x: i32 } + /// let mut p = P { x: 4 }; + /// let c = || { + /// let incr = 10; + /// let nested = || p.x += incr; + /// }; + /// ``` + /// + /// - When reporting the Place back to the Delegate, ensure that the UpvarId uses the enclosing + /// closure as the DefId. + #[instrument(skip(self), level = "debug")] + fn walk_captures(&mut self, closure_expr: ExprId) { + fn upvar_is_local_variable(upvars: UpvarsRef<'_>, var_id: BindingId) -> bool { + upvars.contains(var_id) + } + + // If we have a nested closure, we want to include the fake reads present in the nested + // closure. + // `remove()` then re-insert and not `get()` due to borrowck errors. + if let Some(closure_data) = self.cx.result.closures_data.remove(&closure_expr) { + for (fake_read, cause, origins) in closure_data.fake_reads.iter() { + match fake_read.base { + PlaceBase::Upvar { var_id, closure: _ } => { + if upvar_is_local_variable(self.upvars, var_id) { + // The nested closure might be fake reading the current (enclosing) closure's local variables. + // The only places we want to fake read before creating the parent closure are the ones that + // are not local to it/ defined by it. + // + // ```rust,ignore(cannot-test-this-because-pseudo-code) + // let v1 = (0, 1); + // let c = || { // fake reads: v1 + // let v2 = (0, 1); + // let e = || { // fake reads: v1, v2 + // let (_, t1) = v1; + // let (_, t2) = v2; + // } + // } + // ``` + // This check is performed when visiting the body of the outermost closure (`c`) and ensures + // that we don't add a fake read of v2 in c. + continue; + } + } + _ => { + panic!( + "Do not know how to get ExprId out of Rvalue and StaticItem {:?}", + fake_read.base + ); + } + }; + self.delegate.fake_read( + PlaceWithOrigin { place: fake_read.clone(), origins: origins.clone() }, + *cause, + self.cx, + ); + } + + for (var_id, min_list) in closure_data.min_captures.iter() { + if !self.upvars.contains(*var_id) { + // The nested closure might be capturing the current (enclosing) closure's local variables. + // We check if the root variable is ever mentioned within the enclosing closure, if not + // then for the current body (if it's a closure) these aren't captures, we will ignore them. + continue; + } + for captured_place in min_list { + let place = &captured_place.place; + let capture_info = &captured_place.info; + + // Mark the place to be captured by the enclosing closure + let place_base = + PlaceBase::Upvar { var_id: *var_id, closure: self.closure_expr }; + let place_with_id = PlaceWithOrigin::new( + capture_info.sources.clone(), + place.base_ty.as_ref(), + place_base, + place.projections.clone(), + ); + + match capture_info.capture_kind { + UpvarCapture::ByValue => { + self.consume_or_copy(place_with_id); + } + UpvarCapture::ByUse => { + self.consume_clone_or_copy(place_with_id); + } + UpvarCapture::ByRef(upvar_borrow) => { + self.delegate.borrow(place_with_id, upvar_borrow, self.cx); + } + } + } + } + + self.cx.result.closures_data.insert(closure_expr, closure_data); + } + } + + fn error_reported_in_ty(&self, ty: Ty<'db>) -> Result { + if ty.is_ty_error() { Err(ErrorGuaranteed) } else { Ok(()) } + } +} + +/// The job of the methods whose name starts with `cat_` is to analyze +/// expressions and construct the corresponding [`Place`]s. The `cat` +/// stands for "categorize", this is a leftover from long ago when +/// places were called "categorizations". +/// +/// Note that a [`Place`] differs somewhat from the expression itself. For +/// example, auto-derefs are explicit. Also, an index `a[b]` is decomposed into +/// two operations: a dereference to reach the array data and then an index to +/// jump forward to the relevant item. +impl<'db, D: Delegate<'db>> ExprUseVisitor<'_, '_, 'db, D> { + fn expect_and_resolve_type(&mut self, ty: Option<Ty<'db>>) -> Result<Ty<'db>> { + match ty { + Some(ty) => { + let ty = self.cx.infcx().resolve_vars_if_possible(ty); + self.error_reported_in_ty(ty)?; + Ok(ty) + } + None => Err(ErrorGuaranteed), + } + } + + fn node_ty(&mut self, id: ExprOrPatId) -> Result<Ty<'db>> { + self.expect_and_resolve_type(self.cx.result.type_of_expr_or_pat(id)) + } + + fn expr_ty(&mut self, expr: ExprId) -> Result<Ty<'db>> { + self.node_ty(expr.into()) + } + + fn pat_ty(&mut self, pat: PatId) -> Result<Ty<'db>> { + self.node_ty(pat.into()) + } + + fn expr_ty_adjusted(&mut self, expr: ExprId) -> Result<Ty<'db>> { + self.expect_and_resolve_type(self.cx.result.type_of_expr_with_adjust(expr)) + } + + /// Returns the type of value that this pattern matches against. + /// Some non-obvious cases: + /// + /// - a `ref x` binding matches against a value of type `T` and gives + /// `x` the type `&T`; we return `T`. + /// - a pattern with implicit derefs (thanks to default binding + /// modes #42640) may look like `Some(x)` but in fact have + /// implicit deref patterns attached (e.g., it is really + /// `&Some(x)`). In that case, we return the "outermost" type + /// (e.g., `&Option<T>`). + fn pat_ty_adjusted(&mut self, pat: PatId) -> Result<Ty<'db>> { + // Check for implicit `&` types wrapping the pattern; note + // that these are never attached to binding patterns, so + // actually this is somewhat "disjoint" from the code below + // that aims to account for `ref x`. + if let Some(vec) = self.cx.result.pat_adjustments.get(&pat) + && let Some(first_adjust) = vec.first() + { + debug!("pat_ty(pat={:?}) found adjustment `{:?}`", pat, first_adjust); + return Ok(first_adjust.as_ref()); + } + self.pat_ty_unadjusted(pat) + } + + /// Like [`Self::pat_ty_adjusted`], but ignores implicit `&` patterns. + fn pat_ty_unadjusted(&mut self, pat: PatId) -> Result<Ty<'db>> { + Ok(self.cx.result.pat_ty(pat)) + } + + fn cat_expr(&mut self, expr: ExprId) -> Result<PlaceWithOrigin> { + self.cat_expr_(expr, &self.expr_adjustments(expr)) + } + + /// This recursion helper avoids going through *too many* + /// adjustments, since *only* non-overloaded deref recurses. + fn cat_expr_(&mut self, expr: ExprId, adjustments: &[Adjustment]) -> Result<PlaceWithOrigin> { + match adjustments.split_last() { + None => self.cat_expr_unadjusted(expr), + Some((adjustment, previous)) => { + self.cat_expr_adjusted_with(expr, |this| this.cat_expr_(expr, previous), adjustment) + } + } + } + + fn cat_expr_adjusted( + &mut self, + expr: ExprId, + previous: PlaceWithOrigin, + adjustment: &Adjustment, + ) -> Result<PlaceWithOrigin> { + self.cat_expr_adjusted_with(expr, |_this| Ok(previous), adjustment) + } + + fn cat_expr_adjusted_with<F>( + &mut self, + expr: ExprId, + previous: F, + adjustment: &Adjustment, + ) -> Result<PlaceWithOrigin> + where + F: FnOnce(&mut Self) -> Result<PlaceWithOrigin>, + { + let target = self.cx.infcx().resolve_vars_if_possible(adjustment.target.as_ref()); + match adjustment.kind { + Adjust::Deref(overloaded) => { + // Equivalent to *expr or something similar. + let base = if let Some(deref) = overloaded { + let ref_ty = Ty::new_ref( + self.cx.interner(), + self.cx.types.regions.erased, + target, + deref.0, + ); + self.cat_rvalue(expr.into(), ref_ty) + } else { + previous(self)? + }; + self.cat_deref(expr.into(), base) + } + + Adjust::NeverToAny | Adjust::Pointer(_) | Adjust::Borrow(_) => { + // Result is an rvalue. + Ok(self.cat_rvalue(expr.into(), target)) + } + } + } + + fn cat_expr_unadjusted(&mut self, expr: ExprId) -> Result<PlaceWithOrigin> { + let expr_ty = self.expr_ty(expr)?; + match self.cx.store[expr] { + Expr::UnaryOp { expr: e_base, op: UnaryOp::Deref } => { + if self.cx.result.method_resolutions.contains_key(&expr) { + self.cat_overloaded_place(expr, e_base) + } else { + let base = self.cat_expr(e_base)?; + self.cat_deref(expr.into(), base) + } + } + + Expr::Field { expr: base, .. } => { + let base = self.cat_expr(base)?; + debug!(?base); + + let field_idx = self + .cx + .result + .field_resolutions + .get(&expr) + .map(|field| match *field { + Either::Left(field) => field.local_id.into_raw().into_u32(), + Either::Right(tuple_field) => tuple_field.index, + }) + .ok_or(ErrorGuaranteed)?; + + Ok(self.cat_projection( + expr.into(), + base, + expr_ty, + ProjectionKind::Field { field_idx, variant_idx: 0 }, + )) + } + + Expr::Index { base, index: _ } => { + // rustc checks if this is an overloaded index, but the check is buggy and treats any indexing + // as overloaded, see https://rust-lang.zulipchat.com/#narrow/channel/144729-t-types/topic/.E2.9C.94.20Is.20builtin.20indexing.20any.20special.20in.20typeck.3F/near/565881390. + // So that's what we do here. + self.cat_overloaded_place(expr, base) + } + + Expr::Path(ref path) => { + let resolver_guard = + self.cx.resolver.update_to_inner_scope(self.cx.db, self.cx.owner, expr); + let resolution = self.cx.resolver.resolve_path_in_value_ns_fully( + self.cx.db, + path, + self.cx.store.expr_path_hygiene(expr), + ); + self.cx.resolver.reset_to_guard(resolver_guard); + match (resolution, self.cx.result.assoc_resolutions_for_expr(expr)) { + (_, Some((CandidateId::FunctionId(_) | CandidateId::ConstId(_), _))) + | ( + Some( + ValueNs::ConstId(_) + | ValueNs::GenericParam(_) + | ValueNs::FunctionId(_) + | ValueNs::ImplSelf(_) + | ValueNs::EnumVariantId(_) + | ValueNs::StructId(_), + ), + None, + ) => Ok(self.cat_rvalue(expr.into(), expr_ty)), + (Some(ValueNs::StaticId(_)), None) => Ok(PlaceWithOrigin::new_no_projections( + expr, + expr_ty, + PlaceBase::StaticItem, + )), + (Some(ValueNs::LocalBinding(var_id)), None) => { + self.cat_local(expr.into(), expr_ty, var_id) + } + (None, None) => Err(ErrorGuaranteed), + } + } + + _ => Ok(self.cat_rvalue(expr.into(), expr_ty)), + } + } + + fn cat_local( + &mut self, + id: ExprOrPatId, + expr_ty: Ty<'db>, + var_id: BindingId, + ) -> Result<PlaceWithOrigin> { + if self.upvars.contains(var_id) { + self.cat_upvar(id, var_id) + } else { + Ok(PlaceWithOrigin::new_no_projections(id, expr_ty, PlaceBase::Local(var_id))) + } + } + + /// Categorize an upvar. + /// + /// Note: the actual upvar access contains invisible derefs of closure + /// environment and upvar reference as appropriate. Only regionck cares + /// about these dereferences, so we let it compute them as needed. + fn cat_upvar(&mut self, hir_id: ExprOrPatId, var_id: BindingId) -> Result<PlaceWithOrigin> { + let var_ty = self.expect_and_resolve_type( + self.cx.result.type_of_binding.get(var_id).map(|it| it.as_ref()), + )?; + + Ok(PlaceWithOrigin::new_no_projections( + hir_id, + var_ty, + PlaceBase::Upvar { closure: self.closure_expr, var_id }, + )) + } + + fn cat_rvalue(&self, hir_id: ExprOrPatId, expr_ty: Ty<'db>) -> PlaceWithOrigin { + PlaceWithOrigin::new_no_projections(hir_id, expr_ty, PlaceBase::Rvalue) + } + + fn cat_projection( + &self, + node: ExprOrPatId, + mut base_place: PlaceWithOrigin, + ty: Ty<'db>, + kind: ProjectionKind, + ) -> PlaceWithOrigin { + base_place.push_projection(Projection { kind, ty: ty.store() }, node); + base_place + } + + fn cat_overloaded_place(&mut self, expr: ExprId, base: ExprId) -> Result<PlaceWithOrigin> { + // Reconstruct the output assuming it's a reference with the + // same region and mutability as the receiver. This holds for + // `Deref(Mut)::Deref(_mut)` and `Index(Mut)::index(_mut)`. + let place_ty = self.expr_ty(expr)?; + let base_ty = self.expr_ty_adjusted(base)?; + + let TyKind::Ref(region, _, mutbl) = self.cx.table.structurally_resolve_type(base_ty).kind() + else { + return Err(ErrorGuaranteed); + }; + let ref_ty = Ty::new_ref(self.cx.interner(), region, place_ty, mutbl); + + let base = self.cat_rvalue(expr.into(), ref_ty); + self.cat_deref(expr.into(), base) + } + + fn cat_deref( + &mut self, + node: ExprOrPatId, + mut base_place: PlaceWithOrigin, + ) -> Result<PlaceWithOrigin> { + let base_curr_ty = base_place.place.ty(); + let Some(deref_ty) = + self.cx.table.structurally_resolve_type(base_curr_ty).builtin_deref(true) + else { + debug!("explicit deref of non-derefable type: {:?}", base_curr_ty); + return Err(ErrorGuaranteed); + }; + base_place.push_projection( + Projection { kind: ProjectionKind::Deref, ty: deref_ty.store() }, + node, + ); + Ok(base_place) + } + + /// Returns the variant index for an ADT used within a Struct or TupleStruct pattern + /// Here `pat_hir_id` is the ExprId of the pattern itself. + fn variant_index_for_adt(&self, pat_id: PatId) -> Result<(u32, VariantId)> { + let variant = self.cx.result.variant_resolution_for_pat(pat_id).ok_or(ErrorGuaranteed)?; + let variant_idx = match variant { + VariantId::EnumVariantId(variant) => variant.loc(self.cx.db).index, + VariantId::StructId(_) | VariantId::UnionId(_) => 0, + }; + Ok((variant_idx, variant)) + } + + /// Returns the total number of fields in a tuple used within a Tuple pattern. + /// Here `pat_hir_id` is the ExprId of the pattern itself. + fn total_fields_in_tuple(&mut self, pat_id: PatId) -> usize { + let ty = self.cx.result.pat_ty(pat_id); + match self.cx.table.structurally_resolve_type(ty).kind() { + TyKind::Tuple(args) => args.len(), + _ => panic!("tuple pattern not applied to a tuple"), + } + } + + /// Here, `place` is the `PlaceWithId` being matched and pat is the pattern it + /// is being matched against. + /// + /// In general, the way that this works is that we walk down the pattern, + /// constructing a `PlaceWithId` that represents the path that will be taken + /// to reach the value being matched. + fn cat_pattern<F>( + &mut self, + mut place_with_id: PlaceWithOrigin, + pat: PatId, + op: &mut F, + ) -> Result + where + F: FnMut(&mut Self, PlaceWithOrigin, PatId) -> Result, + { + // If (pattern) adjustments are active for this pattern, adjust the `PlaceWithId` correspondingly. + // `PlaceWithId`s are constructed differently from patterns. For example, in + // + // ``` + // match foo { + // &&Some(x, ) => { ... }, + // _ => { ... }, + // } + // ``` + // + // the pattern `&&Some(x,)` is represented as `Ref { Ref { TupleStruct }}`. To build the + // corresponding `PlaceWithId` we start with the `PlaceWithId` for `foo`, and then, by traversing the + // pattern, try to answer the question: given the address of `foo`, how is `x` reached? + // + // `&&Some(x,)` `place_foo` + // `&Some(x,)` `deref { place_foo}` + // `Some(x,)` `deref { deref { place_foo }}` + // `(x,)` `field0 { deref { deref { place_foo }}}` <- resulting place + // + // The above example has no adjustments. If the code were instead the (after adjustments, + // equivalent) version + // + // ``` + // match foo { + // Some(x, ) => { ... }, + // _ => { ... }, + // } + // ``` + // + // Then we see that to get the same result, we must start with + // `deref { deref { place_foo }}` instead of `place_foo` since the pattern is now `Some(x,)` + // and not `&&Some(x,)`, even though its assigned type is that of `&&Some(x,)`. + let adjustments_len = self.cx.result.pat_adjustment(pat).map_or(0, |it| it.len()); + for _ in 0..adjustments_len { + debug!("applying adjustment to place_with_id={:?}", place_with_id); + // FIXME: We need to adjust this once we implement deref patterns (or pin ergonomics, for that matter). + place_with_id = self.cat_deref(pat.into(), place_with_id)?; + } + let place_with_id = place_with_id; // lose mutability + debug!("applied adjustment derefs to get place_with_id={:?}", place_with_id); + + // Invoke the callback, but only now, after the `place_with_id` has adjusted. + // + // To see that this makes sense, consider `match &Some(3) { Some(x) => { ... }}`. In that + // case, the initial `place_with_id` will be that for `&Some(3)` and the pattern is `Some(x)`. We + // don't want to call `op` with these incompatible values. As written, what happens instead + // is that `op` is called with the adjusted place (that for `*&Some(3)`) and the pattern + // `Some(x)` (which matches). Recursing once more, `*&Some(3)` and the pattern `Some(x)` + // result in the place `Downcast<Some>(*&Some(3)).0` associated to `x` and invoke `op` with + // that (where the `ref` on `x` is implied). + op(self, place_with_id.clone(), pat)?; + + match self.cx.store[pat] { + Pat::Tuple { args: ref subpats, ellipsis: dots_pos } => { + // (p1, ..., pN) + let total_fields = self.total_fields_in_tuple(pat); + + for (i, &subpat) in subpats.iter().enumerate_and_adjust(total_fields, dots_pos) { + let subpat_ty = self.pat_ty_adjusted(subpat)?; + let projection_kind = + ProjectionKind::Field { field_idx: i as u32, variant_idx: 0 }; + let sub_place = self.cat_projection( + pat.into(), + place_with_id.clone(), + subpat_ty, + projection_kind, + ); + self.cat_pattern(sub_place, subpat, op)?; + } + } + + Pat::TupleStruct { args: ref subpats, ellipsis: dots_pos, .. } => { + // S(p1, ..., pN) + let (variant_index, variant) = self.variant_index_for_adt(pat)?; + let total_fields = variant.fields(self.cx.db).len(); + + for (i, &subpat) in subpats.iter().enumerate_and_adjust(total_fields, dots_pos) { + let subpat_ty = self.pat_ty_adjusted(subpat)?; + let projection_kind = + ProjectionKind::Field { variant_idx: variant_index, field_idx: i as u32 }; + let sub_place = self.cat_projection( + pat.into(), + place_with_id.clone(), + subpat_ty, + projection_kind, + ); + self.cat_pattern(sub_place, subpat, op)?; + } + } + + Pat::Record { args: ref field_pats, .. } => { + // S { f1: p1, ..., fN: pN } + + let (variant_index, variant) = self.variant_index_for_adt(pat)?; + let fields = variant.fields(self.cx.db); + + for fp in field_pats { + let field_ty = self.pat_ty_adjusted(fp.pat)?; + let field_index = fields.field(&fp.name).ok_or(ErrorGuaranteed)?; + + let field_place = self.cat_projection( + pat.into(), + place_with_id.clone(), + field_ty, + ProjectionKind::Field { + variant_idx: variant_index, + field_idx: field_index.into_raw().into_u32(), + }, + ); + self.cat_pattern(field_place, fp.pat, op)?; + } + } + + Pat::Or(ref pats) => { + for &pat in pats { + self.cat_pattern(place_with_id.clone(), pat, op)?; + } + } + + Pat::Bind { subpat: Some(subpat), .. } => { + self.cat_pattern(place_with_id, subpat, op)?; + } + + Pat::Box { inner: subpat } | Pat::Ref { pat: subpat, .. } => { + // box p1, &p1, &mut p1. we can ignore the mutability of + // PatKind::Ref since that information is already contained + // in the type. + let subplace = self.cat_deref(pat.into(), place_with_id)?; + self.cat_pattern(subplace, subpat, op)?; + } + + Pat::Slice { prefix: ref before, slice, suffix: ref after } => { + let Some(element_ty) = self + .cx + .table + .structurally_resolve_type(place_with_id.place.ty()) + .builtin_index() + else { + debug!("explicit index of non-indexable type {:?}", place_with_id); + panic!("explicit index of non-indexable type"); + }; + let elt_place = self.cat_projection( + pat.into(), + place_with_id.clone(), + element_ty, + ProjectionKind::Index, + ); + for &before_pat in before { + self.cat_pattern(elt_place.clone(), before_pat, op)?; + } + if let Some(slice_pat) = slice { + let slice_pat_ty = self.pat_ty_adjusted(slice_pat)?; + let slice_place = self.cat_projection( + pat.into(), + place_with_id, + slice_pat_ty, + ProjectionKind::Subslice, + ); + self.cat_pattern(slice_place, slice_pat, op)?; + } + for &after_pat in after { + self.cat_pattern(elt_place.clone(), after_pat, op)?; + } + } + + Pat::Bind { subpat: None, .. } + | Pat::Expr(..) + | Pat::Path(_) + | Pat::Lit(..) + | Pat::ConstBlock(..) + | Pat::Range { .. } + | Pat::Missing + | Pat::Wild => { + // always ok + } + } + + Ok(()) + } + + /// Checks whether a type has multiple variants, and therefore, whether a + /// read of the discriminant might be necessary. Note that the actual MIR + /// builder code does a more specific check, filtering out variants that + /// happen to be uninhabited. + /// + /// Here, it is not practical to perform such a check, because inhabitedness + /// queries require typeck results, and typeck requires closure capture analysis. + /// + /// Moreover, the language is moving towards uninhabited variants still semantically + /// causing a discriminant read, so we *shouldn't* perform any such check. + /// + /// FIXME(never_patterns): update this comment once the aforementioned MIR builder + /// code is changed to be insensitive to inhhabitedness. + #[instrument(skip(self), level = "debug")] + fn is_multivariant_adt(&mut self, ty: Ty<'db>) -> bool { + if let TyKind::Adt(def, _) = self.cx.table.structurally_resolve_type(ty).kind() { + // Note that if a non-exhaustive SingleVariant is defined in another crate, we need + // to assume that more cases will be added to the variant in the future. This mean + // that we should handle non-exhaustive SingleVariant the same way we would handle + // a MultiVariant. + match def.def_id().0 { + AdtId::StructId(_) | AdtId::UnionId(_) => false, + AdtId::EnumId(did) => { + let has_foreign_non_exhaustive = || { + AttrFlags::query(self.cx.db, did.into()).contains(AttrFlags::NON_EXHAUSTIVE) + && did.krate(self.cx.db) != self.cx.krate() + }; + did.enum_variants(self.cx.db).variants.len() > 1 || has_foreign_non_exhaustive() + } + } + } else { + false + } + } +} |