| //! 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. |
| |
| use std::cell::{Ref, RefCell}; |
| use std::ops::Deref; |
| use std::slice::from_ref; |
| |
| use hir::def::DefKind; |
| use hir::pat_util::EnumerateAndAdjustIterator as _; |
| use hir::Expr; |
| use rustc_lint::LateContext; |
| // Export these here so that Clippy can use them. |
| pub use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, Projection}; |
| |
| use rustc_data_structures::fx::FxIndexMap; |
| use rustc_hir as hir; |
| use rustc_hir::def::{CtorOf, Res}; |
| use rustc_hir::def_id::LocalDefId; |
| use rustc_hir::{HirId, PatKind}; |
| use rustc_middle::hir::place::ProjectionKind; |
| use rustc_middle::mir::FakeReadCause; |
| use rustc_middle::ty::{ |
| self, adjustment, AdtKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt as _, |
| }; |
| use rustc_middle::{bug, span_bug}; |
| use rustc_span::{ErrorGuaranteed, Span}; |
| use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT}; |
| use rustc_trait_selection::infer::InferCtxtExt; |
| use ty::BorrowKind::ImmBorrow; |
| |
| use crate::fn_ctxt::FnCtxt; |
| |
| /// This trait defines the callbacks you can expect to receive when |
| /// employing the ExprUseVisitor. |
| pub trait Delegate<'tcx> { |
| /// The value found at `place` is moved, depending |
| /// on `mode`. Where `diag_expr_id` is the id used for diagnostics for `place`. |
| /// |
| /// Use of a `Copy` type in a ByValue 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 consume(&mut self, place_with_id: &PlaceWithHirId<'tcx>, diag_expr_id: HirId); |
| |
| /// 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: &PlaceWithHirId<'tcx>, |
| diag_expr_id: HirId, |
| bk: ty::BorrowKind, |
| ); |
| |
| /// The value found at `place` is being copied. |
| /// `diag_expr_id` is the id used for diagnostics (see `consume` for more details). |
| fn copy(&mut self, place_with_id: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| // 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, diag_expr_id, ty::BorrowKind::ImmBorrow) |
| } |
| |
| /// 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: &PlaceWithHirId<'tcx>, diag_expr_id: HirId); |
| |
| /// 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: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| // Bindings can normally be treated as a regular assignment, so by default we |
| // forward this to the mutate callback. |
| self.mutate(binding_place, diag_expr_id) |
| } |
| |
| /// The `place` should be a fake read because of specified `cause`. |
| fn fake_read( |
| &mut self, |
| place_with_id: &PlaceWithHirId<'tcx>, |
| cause: FakeReadCause, |
| diag_expr_id: HirId, |
| ); |
| } |
| |
| impl<'tcx, D: Delegate<'tcx>> Delegate<'tcx> for &mut D { |
| fn consume(&mut self, place_with_id: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| (**self).consume(place_with_id, diag_expr_id) |
| } |
| |
| fn borrow( |
| &mut self, |
| place_with_id: &PlaceWithHirId<'tcx>, |
| diag_expr_id: HirId, |
| bk: ty::BorrowKind, |
| ) { |
| (**self).borrow(place_with_id, diag_expr_id, bk) |
| } |
| |
| fn copy(&mut self, place_with_id: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| (**self).copy(place_with_id, diag_expr_id) |
| } |
| |
| fn mutate(&mut self, assignee_place: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| (**self).mutate(assignee_place, diag_expr_id) |
| } |
| |
| fn bind(&mut self, binding_place: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| (**self).bind(binding_place, diag_expr_id) |
| } |
| |
| fn fake_read( |
| &mut self, |
| place_with_id: &PlaceWithHirId<'tcx>, |
| cause: FakeReadCause, |
| diag_expr_id: HirId, |
| ) { |
| (**self).fake_read(place_with_id, cause, diag_expr_id) |
| } |
| } |
| |
| pub trait TypeInformationCtxt<'tcx> { |
| type TypeckResults<'a>: Deref<Target = ty::TypeckResults<'tcx>> |
| where |
| Self: 'a; |
| |
| type Error; |
| |
| fn typeck_results(&self) -> Self::TypeckResults<'_>; |
| |
| fn resolve_vars_if_possible<T: TypeFoldable<TyCtxt<'tcx>>>(&self, t: T) -> T; |
| |
| fn try_structurally_resolve_type(&self, span: Span, ty: Ty<'tcx>) -> Ty<'tcx>; |
| |
| fn report_error(&self, span: Span, msg: impl ToString) -> Self::Error; |
| |
| fn error_reported_in_ty(&self, ty: Ty<'tcx>) -> Result<(), Self::Error>; |
| |
| fn tainted_by_errors(&self) -> Result<(), Self::Error>; |
| |
| fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool; |
| |
| fn body_owner_def_id(&self) -> LocalDefId; |
| |
| fn tcx(&self) -> TyCtxt<'tcx>; |
| } |
| |
| impl<'tcx> TypeInformationCtxt<'tcx> for &FnCtxt<'_, 'tcx> { |
| type TypeckResults<'a> = Ref<'a, ty::TypeckResults<'tcx>> |
| where |
| Self: 'a; |
| |
| type Error = ErrorGuaranteed; |
| |
| fn typeck_results(&self) -> Self::TypeckResults<'_> { |
| self.typeck_results.borrow() |
| } |
| |
| fn resolve_vars_if_possible<T: TypeFoldable<TyCtxt<'tcx>>>(&self, t: T) -> T { |
| self.infcx.resolve_vars_if_possible(t) |
| } |
| |
| fn try_structurally_resolve_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { |
| (**self).try_structurally_resolve_type(sp, ty) |
| } |
| |
| fn report_error(&self, span: Span, msg: impl ToString) -> Self::Error { |
| self.tcx.dcx().span_delayed_bug(span, msg.to_string()) |
| } |
| |
| fn error_reported_in_ty(&self, ty: Ty<'tcx>) -> Result<(), Self::Error> { |
| ty.error_reported() |
| } |
| |
| fn tainted_by_errors(&self) -> Result<(), ErrorGuaranteed> { |
| if let Some(guar) = self.infcx.tainted_by_errors() { Err(guar) } else { Ok(()) } |
| } |
| |
| fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool { |
| self.infcx.type_is_copy_modulo_regions(self.param_env, ty) |
| } |
| |
| fn body_owner_def_id(&self) -> LocalDefId { |
| self.body_id |
| } |
| |
| fn tcx(&self) -> TyCtxt<'tcx> { |
| self.tcx |
| } |
| } |
| |
| impl<'tcx> TypeInformationCtxt<'tcx> for (&LateContext<'tcx>, LocalDefId) { |
| type TypeckResults<'a> = &'tcx ty::TypeckResults<'tcx> |
| where |
| Self: 'a; |
| |
| type Error = !; |
| |
| fn typeck_results(&self) -> Self::TypeckResults<'_> { |
| self.0.maybe_typeck_results().expect("expected typeck results") |
| } |
| |
| fn try_structurally_resolve_type(&self, _span: Span, ty: Ty<'tcx>) -> Ty<'tcx> { |
| // FIXME: Maybe need to normalize here. |
| ty |
| } |
| |
| fn resolve_vars_if_possible<T: TypeFoldable<TyCtxt<'tcx>>>(&self, t: T) -> T { |
| t |
| } |
| |
| fn report_error(&self, span: Span, msg: impl ToString) -> ! { |
| span_bug!(span, "{}", msg.to_string()) |
| } |
| |
| fn error_reported_in_ty(&self, _ty: Ty<'tcx>) -> Result<(), !> { |
| Ok(()) |
| } |
| |
| fn tainted_by_errors(&self) -> Result<(), !> { |
| Ok(()) |
| } |
| |
| fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool { |
| ty.is_copy_modulo_regions(self.0.tcx, self.0.param_env) |
| } |
| |
| fn body_owner_def_id(&self) -> LocalDefId { |
| self.1 |
| } |
| |
| fn tcx(&self) -> TyCtxt<'tcx> { |
| self.0.tcx |
| } |
| } |
| |
| /// The ExprUseVisitor type |
| /// |
| /// This is the code that actually walks the tree. |
| pub struct ExprUseVisitor<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> { |
| cx: Cx, |
| /// We use a `RefCell` here so that delegates can mutate themselves, but we can |
| /// still have calls to our own helper functions. |
| delegate: RefCell<D>, |
| upvars: Option<&'tcx FxIndexMap<HirId, hir::Upvar>>, |
| } |
| |
| impl<'a, 'tcx, D: Delegate<'tcx>> ExprUseVisitor<'tcx, (&'a LateContext<'tcx>, LocalDefId), D> { |
| pub fn for_clippy(cx: &'a LateContext<'tcx>, body_def_id: LocalDefId, delegate: D) -> Self { |
| Self::new((cx, body_def_id), delegate) |
| } |
| } |
| |
| impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx, Cx, 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: Cx, delegate: D) -> Self { |
| ExprUseVisitor { |
| delegate: RefCell::new(delegate), |
| upvars: cx.tcx().upvars_mentioned(cx.body_owner_def_id()), |
| cx, |
| } |
| } |
| |
| pub fn consume_body(&self, body: &hir::Body<'_>) -> Result<(), Cx::Error> { |
| for param in body.params { |
| let param_ty = self.pat_ty_adjusted(param.pat)?; |
| debug!("consume_body: param_ty = {:?}", param_ty); |
| |
| let param_place = self.cat_rvalue(param.hir_id, param_ty); |
| |
| self.walk_irrefutable_pat(¶m_place, param.pat)?; |
| } |
| |
| self.consume_expr(body.value)?; |
| |
| Ok(()) |
| } |
| |
| fn consume_or_copy(&self, place_with_id: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| debug!("delegate_consume(place_with_id={:?})", place_with_id); |
| |
| if self.cx.type_is_copy_modulo_regions(place_with_id.place.ty()) { |
| self.delegate.borrow_mut().copy(place_with_id, diag_expr_id); |
| } else { |
| self.delegate.borrow_mut().consume(place_with_id, diag_expr_id); |
| } |
| } |
| |
| fn consume_exprs(&self, exprs: &[hir::Expr<'_>]) -> Result<(), Cx::Error> { |
| for expr in exprs { |
| self.consume_expr(expr)?; |
| } |
| |
| Ok(()) |
| } |
| |
| // FIXME: It's suspicious that this is public; clippy should probably use `walk_expr`. |
| pub fn consume_expr(&self, expr: &hir::Expr<'_>) -> Result<(), Cx::Error> { |
| debug!("consume_expr(expr={:?})", expr); |
| |
| let place_with_id = self.cat_expr(expr)?; |
| self.consume_or_copy(&place_with_id, place_with_id.hir_id); |
| self.walk_expr(expr)?; |
| Ok(()) |
| } |
| |
| fn mutate_expr(&self, expr: &hir::Expr<'_>) -> Result<(), Cx::Error> { |
| let place_with_id = self.cat_expr(expr)?; |
| self.delegate.borrow_mut().mutate(&place_with_id, place_with_id.hir_id); |
| self.walk_expr(expr)?; |
| Ok(()) |
| } |
| |
| fn borrow_expr(&self, expr: &hir::Expr<'_>, bk: ty::BorrowKind) -> Result<(), Cx::Error> { |
| debug!("borrow_expr(expr={:?}, bk={:?})", expr, bk); |
| |
| let place_with_id = self.cat_expr(expr)?; |
| self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk); |
| self.walk_expr(expr) |
| } |
| |
| pub fn walk_expr(&self, expr: &hir::Expr<'_>) -> Result<(), Cx::Error> { |
| debug!("walk_expr(expr={:?})", expr); |
| |
| self.walk_adjustment(expr)?; |
| |
| match expr.kind { |
| hir::ExprKind::Path(_) => {} |
| |
| hir::ExprKind::Type(subexpr, _) => { |
| self.walk_expr(subexpr)?; |
| } |
| |
| hir::ExprKind::Unary(hir::UnOp::Deref, base) => { |
| // *base |
| self.walk_expr(base)?; |
| } |
| |
| hir::ExprKind::Field(base, _) => { |
| // base.f |
| self.walk_expr(base)?; |
| } |
| |
| hir::ExprKind::Index(lhs, rhs, _) => { |
| // lhs[rhs] |
| self.walk_expr(lhs)?; |
| self.consume_expr(rhs)?; |
| } |
| |
| hir::ExprKind::Call(callee, args) => { |
| // callee(args) |
| self.consume_expr(callee)?; |
| self.consume_exprs(args)?; |
| } |
| |
| hir::ExprKind::MethodCall(.., receiver, args, _) => { |
| // callee.m(args) |
| self.consume_expr(receiver)?; |
| self.consume_exprs(args)?; |
| } |
| |
| hir::ExprKind::Struct(_, fields, ref opt_with) => { |
| self.walk_struct_expr(fields, opt_with)?; |
| } |
| |
| hir::ExprKind::Tup(exprs) => { |
| self.consume_exprs(exprs)?; |
| } |
| |
| hir::ExprKind::If(cond_expr, then_expr, ref 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)?; |
| } |
| } |
| |
| hir::ExprKind::Let(hir::LetExpr { pat, init, .. }) => { |
| self.walk_local(init, pat, None, || self.borrow_expr(init, ty::ImmBorrow))?; |
| } |
| |
| hir::ExprKind::Match(discr, arms, _) => { |
| let discr_place = self.cat_expr(discr)?; |
| self.maybe_read_scrutinee( |
| discr, |
| discr_place.clone(), |
| arms.iter().map(|arm| arm.pat), |
| )?; |
| |
| // treatment of the discriminant is handled while walking the arms. |
| for arm in arms { |
| self.walk_arm(&discr_place, arm)?; |
| } |
| } |
| |
| hir::ExprKind::Array(exprs) => { |
| self.consume_exprs(exprs)?; |
| } |
| |
| hir::ExprKind::AddrOf(_, m, base) => { |
| // &base |
| // make sure that the thing we are pointing out stays valid |
| // for the lifetime `scope_r` of the resulting ptr: |
| let bk = ty::BorrowKind::from_mutbl(m); |
| self.borrow_expr(base, bk)?; |
| } |
| |
| hir::ExprKind::InlineAsm(asm) => { |
| for (op, _op_sp) in asm.operands { |
| match op { |
| hir::InlineAsmOperand::In { expr, .. } => { |
| self.consume_expr(expr)?; |
| } |
| hir::InlineAsmOperand::Out { expr: Some(expr), .. } |
| | hir::InlineAsmOperand::InOut { expr, .. } => { |
| self.mutate_expr(expr)?; |
| } |
| hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { |
| self.consume_expr(in_expr)?; |
| if let Some(out_expr) = out_expr { |
| self.mutate_expr(out_expr)?; |
| } |
| } |
| hir::InlineAsmOperand::Out { expr: None, .. } |
| | hir::InlineAsmOperand::Const { .. } |
| | hir::InlineAsmOperand::SymFn { .. } |
| | hir::InlineAsmOperand::SymStatic { .. } => {} |
| hir::InlineAsmOperand::Label { block } => { |
| self.walk_block(block)?; |
| } |
| } |
| } |
| } |
| |
| hir::ExprKind::Continue(..) |
| | hir::ExprKind::Lit(..) |
| | hir::ExprKind::ConstBlock(..) |
| | hir::ExprKind::OffsetOf(..) |
| | hir::ExprKind::Err(_) => {} |
| |
| hir::ExprKind::Loop(blk, ..) => { |
| self.walk_block(blk)?; |
| } |
| |
| hir::ExprKind::Unary(_, lhs) => { |
| self.consume_expr(lhs)?; |
| } |
| |
| hir::ExprKind::Binary(_, lhs, rhs) => { |
| self.consume_expr(lhs)?; |
| self.consume_expr(rhs)?; |
| } |
| |
| hir::ExprKind::Block(blk, _) => { |
| self.walk_block(blk)?; |
| } |
| |
| hir::ExprKind::Break(_, ref opt_expr) | hir::ExprKind::Ret(ref opt_expr) => { |
| if let Some(expr) = *opt_expr { |
| self.consume_expr(expr)?; |
| } |
| } |
| |
| hir::ExprKind::Become(call) => { |
| self.consume_expr(call)?; |
| } |
| |
| hir::ExprKind::Assign(lhs, rhs, _) => { |
| self.mutate_expr(lhs)?; |
| self.consume_expr(rhs)?; |
| } |
| |
| hir::ExprKind::Cast(base, _) => { |
| self.consume_expr(base)?; |
| } |
| |
| hir::ExprKind::DropTemps(expr) => { |
| self.consume_expr(expr)?; |
| } |
| |
| hir::ExprKind::AssignOp(_, lhs, rhs) => { |
| if self.cx.typeck_results().is_method_call(expr) { |
| self.consume_expr(lhs)?; |
| } else { |
| self.mutate_expr(lhs)?; |
| } |
| self.consume_expr(rhs)?; |
| } |
| |
| hir::ExprKind::Repeat(base, _) => { |
| self.consume_expr(base)?; |
| } |
| |
| hir::ExprKind::Closure(closure) => { |
| self.walk_captures(closure)?; |
| } |
| |
| hir::ExprKind::Yield(value, _) => { |
| self.consume_expr(value)?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn walk_stmt(&self, stmt: &hir::Stmt<'_>) -> Result<(), Cx::Error> { |
| match stmt.kind { |
| hir::StmtKind::Let(hir::LetStmt { pat, init: Some(expr), els, .. }) => { |
| self.walk_local(expr, pat, *els, || Ok(()))?; |
| } |
| |
| hir::StmtKind::Let(_) => {} |
| |
| hir::StmtKind::Item(_) => { |
| // We don't visit nested items in this visitor, |
| // only the fn body we were given. |
| } |
| |
| hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) => { |
| self.consume_expr(expr)?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn maybe_read_scrutinee<'t>( |
| &self, |
| discr: &Expr<'_>, |
| discr_place: PlaceWithHirId<'tcx>, |
| pats: impl Iterator<Item = &'t hir::Pat<'t>>, |
| ) -> Result<(), Cx::Error> { |
| // Matching should not always be considered a use of the place, hence |
| // discr does not necessarily need to be borrowed. |
| // We only want to borrow discr if the pattern contain something other |
| // than wildcards. |
| let mut needs_to_be_read = false; |
| for pat in pats { |
| self.cat_pattern(discr_place.clone(), pat, &mut |place, pat| { |
| match &pat.kind { |
| PatKind::Binding(.., opt_sub_pat) => { |
| // If the opt_sub_pat is None, then the binding does not count as |
| // a wildcard for the purpose of borrowing discr. |
| if opt_sub_pat.is_none() { |
| needs_to_be_read = true; |
| } |
| } |
| PatKind::Never => { |
| // A never pattern reads the value. |
| // FIXME(never_patterns): does this do what I expect? |
| needs_to_be_read = true; |
| } |
| PatKind::Path(qpath) => { |
| // A `Path` pattern is just a name like `Foo`. This is either a |
| // named constant or else it refers to an ADT variant |
| |
| let res = self.cx.typeck_results().qpath_res(qpath, pat.hir_id); |
| match res { |
| Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => { |
| // Named constants have to be equated with the value |
| // being matched, so that's a read of the value being matched. |
| // |
| // FIXME: We don't actually reads for ZSTs. |
| needs_to_be_read = true; |
| } |
| _ => { |
| // Otherwise, this is a struct/enum variant, and so it's |
| // only a read if we need to read the discriminant. |
| needs_to_be_read |= |
| self.is_multivariant_adt(place.place.ty(), pat.span); |
| } |
| } |
| } |
| PatKind::TupleStruct(..) | PatKind::Struct(..) | PatKind::Tuple(..) => { |
| // For `Foo(..)`, `Foo { ... }` and `(...)` patterns, check if we are matching |
| // against a multivariant enum or struct. In that case, we have to read |
| // the discriminant. Otherwise this kind of pattern doesn't actually |
| // read anything (we'll get invoked for the `...`, which may indeed |
| // perform some reads). |
| |
| let place_ty = place.place.ty(); |
| needs_to_be_read |= self.is_multivariant_adt(place_ty, pat.span); |
| } |
| PatKind::Lit(_) | PatKind::Range(..) => { |
| // If the PatKind is a Lit or a Range then we want |
| // to borrow discr. |
| needs_to_be_read = true; |
| } |
| PatKind::Slice(lhs, wild, 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().peel_refs().is_array() |
| { |
| } else { |
| needs_to_be_read = true; |
| } |
| } |
| PatKind::Or(_) |
| | PatKind::Box(_) |
| | PatKind::Deref(_) |
| | PatKind::Ref(..) |
| | PatKind::Wild |
| | PatKind::Err(_) => { |
| // If the PatKind is Or, Box, or Ref, the decision is made later |
| // as these patterns contains subpatterns |
| // If the PatKind is Wild or Err, the decision is made based on the other patterns |
| // being examined |
| } |
| } |
| |
| Ok(()) |
| })? |
| } |
| |
| if needs_to_be_read { |
| self.borrow_expr(discr, ty::ImmBorrow)?; |
| } else { |
| let closure_def_id = match discr_place.place.base { |
| PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id), |
| _ => None, |
| }; |
| |
| self.delegate.borrow_mut().fake_read( |
| &discr_place, |
| FakeReadCause::ForMatchedPlace(closure_def_id), |
| discr_place.hir_id, |
| ); |
| |
| // We always want to walk the discriminant. We want to make sure, for instance, |
| // that the discriminant has been initialized. |
| self.walk_expr(discr)?; |
| } |
| Ok(()) |
| } |
| |
| fn walk_local<F>( |
| &self, |
| expr: &hir::Expr<'_>, |
| pat: &hir::Pat<'_>, |
| els: Option<&hir::Block<'_>>, |
| mut f: F, |
| ) -> Result<(), Cx::Error> |
| where |
| F: FnMut() -> Result<(), Cx::Error>, |
| { |
| self.walk_expr(expr)?; |
| let expr_place = self.cat_expr(expr)?; |
| f()?; |
| if let Some(els) = els { |
| // borrowing because we need to test the discriminant |
| self.maybe_read_scrutinee(expr, expr_place.clone(), from_ref(pat).iter())?; |
| self.walk_block(els)?; |
| } |
| self.walk_irrefutable_pat(&expr_place, pat)?; |
| Ok(()) |
| } |
| |
| /// Indicates that the value of `blk` will be consumed, meaning either copied or moved |
| /// depending on its type. |
| fn walk_block(&self, blk: &hir::Block<'_>) -> Result<(), Cx::Error> { |
| debug!("walk_block(blk.hir_id={})", blk.hir_id); |
| |
| for stmt in blk.stmts { |
| self.walk_stmt(stmt)?; |
| } |
| |
| if let Some(tail_expr) = blk.expr { |
| self.consume_expr(tail_expr)?; |
| } |
| |
| Ok(()) |
| } |
| |
| fn walk_struct_expr<'hir>( |
| &self, |
| fields: &[hir::ExprField<'_>], |
| opt_with: &Option<&'hir hir::Expr<'_>>, |
| ) -> Result<(), Cx::Error> { |
| // Consume the expressions supplying values for each field. |
| for field in fields { |
| self.consume_expr(field.expr)?; |
| |
| // The struct path probably didn't resolve |
| if self.cx.typeck_results().opt_field_index(field.hir_id).is_none() { |
| self.cx |
| .tcx() |
| .dcx() |
| .span_delayed_bug(field.span, "couldn't resolve index for field"); |
| } |
| } |
| |
| let with_expr = match *opt_with { |
| Some(w) => &*w, |
| None => { |
| 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.try_structurally_resolve_type(with_expr.span, with_place.place.ty()).kind() { |
| ty::Adt(adt, args) if adt.is_struct() => { |
| // Consume those fields of the with expression that are needed. |
| for (f_index, with_field) in adt.non_enum_variant().fields.iter_enumerated() { |
| let is_mentioned = fields.iter().any(|f| { |
| self.cx.typeck_results().opt_field_index(f.hir_id) == Some(f_index) |
| }); |
| if !is_mentioned { |
| let field_place = self.cat_projection( |
| with_expr.hir_id, |
| with_place.clone(), |
| with_field.ty(self.cx.tcx(), args), |
| ProjectionKind::Field(f_index, FIRST_VARIANT), |
| ); |
| self.consume_or_copy(&field_place, field_place.hir_id); |
| } |
| } |
| } |
| _ => { |
| // the base expression should always evaluate to a |
| // struct; however, when EUV is run during typeck, it |
| // may not. This will generate an error earlier in typeck, |
| // so we can just ignore it. |
| if self.cx.tcx().dcx().has_errors().is_none() { |
| span_bug!(with_expr.span, "with expression doesn't evaluate to a struct"); |
| } |
| } |
| } |
| |
| // walk the with expression so that complex expressions |
| // are properly handled. |
| self.walk_expr(with_expr)?; |
| |
| Ok(()) |
| } |
| |
| /// Invoke the appropriate delegate calls for anything that gets |
| /// consumed or borrowed as part of the automatic adjustment |
| /// process. |
| fn walk_adjustment(&self, expr: &hir::Expr<'_>) -> Result<(), Cx::Error> { |
| let typeck_results = self.cx.typeck_results(); |
| let adjustments = typeck_results.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 { |
| adjustment::Adjust::NeverToAny |
| | adjustment::Adjust::Pointer(_) |
| | adjustment::Adjust::DynStar => { |
| // Creating a closure/fn-pointer or unsizing consumes |
| // the input and stores it into the resulting rvalue. |
| self.consume_or_copy(&place_with_id, place_with_id.hir_id); |
| } |
| |
| adjustment::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`. |
| adjustment::Adjust::Deref(Some(ref deref)) => { |
| let bk = ty::BorrowKind::from_mutbl(deref.mutbl); |
| self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk); |
| } |
| |
| adjustment::Adjust::Borrow(ref autoref) => { |
| self.walk_autoref(expr, &place_with_id, 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 the mem-categorized form of `expr` |
| /// after all relevant autoderefs have occurred. |
| fn walk_autoref( |
| &self, |
| expr: &hir::Expr<'_>, |
| base_place: &PlaceWithHirId<'tcx>, |
| autoref: &adjustment::AutoBorrow<'tcx>, |
| ) { |
| debug!( |
| "walk_autoref(expr.hir_id={} base_place={:?} autoref={:?})", |
| expr.hir_id, base_place, autoref |
| ); |
| |
| match *autoref { |
| adjustment::AutoBorrow::Ref(_, m) => { |
| self.delegate.borrow_mut().borrow( |
| base_place, |
| base_place.hir_id, |
| ty::BorrowKind::from_mutbl(m.into()), |
| ); |
| } |
| |
| adjustment::AutoBorrow::RawPtr(m) => { |
| debug!("walk_autoref: expr.hir_id={} base_place={:?}", expr.hir_id, base_place); |
| |
| self.delegate.borrow_mut().borrow( |
| base_place, |
| base_place.hir_id, |
| ty::BorrowKind::from_mutbl(m), |
| ); |
| } |
| } |
| } |
| |
| fn walk_arm( |
| &self, |
| discr_place: &PlaceWithHirId<'tcx>, |
| arm: &hir::Arm<'_>, |
| ) -> Result<(), Cx::Error> { |
| let closure_def_id = match discr_place.place.base { |
| PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id), |
| _ => None, |
| }; |
| |
| self.delegate.borrow_mut().fake_read( |
| discr_place, |
| FakeReadCause::ForMatchedPlace(closure_def_id), |
| discr_place.hir_id, |
| ); |
| self.walk_pat(discr_place, arm.pat, arm.guard.is_some())?; |
| |
| if let Some(ref e) = arm.guard { |
| self.consume_expr(e)?; |
| } |
| |
| self.consume_expr(arm.body)?; |
| Ok(()) |
| } |
| |
| /// Walks a pat that occurs in isolation (i.e., top-level of fn argument or |
| /// let binding, and *not* a match arm or nested pat.) |
| fn walk_irrefutable_pat( |
| &self, |
| discr_place: &PlaceWithHirId<'tcx>, |
| pat: &hir::Pat<'_>, |
| ) -> Result<(), Cx::Error> { |
| let closure_def_id = match discr_place.place.base { |
| PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id), |
| _ => None, |
| }; |
| |
| self.delegate.borrow_mut().fake_read( |
| discr_place, |
| FakeReadCause::ForLet(closure_def_id), |
| discr_place.hir_id, |
| ); |
| self.walk_pat(discr_place, pat, false)?; |
| Ok(()) |
| } |
| |
| /// The core driver for walking a pattern |
| fn walk_pat( |
| &self, |
| discr_place: &PlaceWithHirId<'tcx>, |
| pat: &hir::Pat<'_>, |
| has_guard: bool, |
| ) -> Result<(), Cx::Error> { |
| debug!("walk_pat(discr_place={:?}, pat={:?}, has_guard={:?})", discr_place, pat, has_guard); |
| |
| let tcx = self.cx.tcx(); |
| self.cat_pattern(discr_place.clone(), pat, &mut |place, pat| { |
| if let PatKind::Binding(_, canonical_id, ..) = pat.kind { |
| debug!("walk_pat: binding place={:?} pat={:?}", place, pat); |
| if let Some(bm) = |
| self.cx.typeck_results().extract_binding_mode(tcx.sess, pat.hir_id, pat.span) |
| { |
| debug!("walk_pat: pat.hir_id={:?} bm={:?}", pat.hir_id, bm); |
| |
| // pat_ty: the type of the binding being produced. |
| let pat_ty = self.node_ty(pat.hir_id)?; |
| debug!("walk_pat: pat_ty={:?}", pat_ty); |
| |
| let def = Res::Local(canonical_id); |
| if let Ok(ref binding_place) = self.cat_res(pat.hir_id, pat.span, pat_ty, def) { |
| self.delegate.borrow_mut().bind(binding_place, binding_place.hir_id); |
| } |
| |
| // 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 { |
| self.delegate.borrow_mut().borrow(place, discr_place.hir_id, ImmBorrow); |
| } |
| |
| // 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 bm.0 { |
| hir::ByRef::Yes(m) => { |
| let bk = ty::BorrowKind::from_mutbl(m); |
| self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk); |
| } |
| hir::ByRef::No => { |
| debug!("walk_pat binding consuming pat"); |
| self.consume_or_copy(place, discr_place.hir_id); |
| } |
| } |
| } |
| } else if let PatKind::Deref(subpattern) = pat.kind { |
| // A deref pattern is a bit special: the binding mode of its inner bindings |
| // determines whether to borrow *at the level of the deref pattern* rather than |
| // borrowing the bound place (since that inner place is inside the temporary that |
| // stores the result of calling `deref()`/`deref_mut()` so can't be captured). |
| let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(subpattern); |
| let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not }; |
| let bk = ty::BorrowKind::from_mutbl(mutability); |
| self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk); |
| } |
| |
| 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. |
| fn walk_captures(&self, closure_expr: &hir::Closure<'_>) -> Result<(), Cx::Error> { |
| fn upvar_is_local_variable( |
| upvars: Option<&FxIndexMap<HirId, hir::Upvar>>, |
| upvar_id: HirId, |
| body_owner_is_closure: bool, |
| ) -> bool { |
| upvars.map(|upvars| !upvars.contains_key(&upvar_id)).unwrap_or(body_owner_is_closure) |
| } |
| |
| debug!("walk_captures({:?})", closure_expr); |
| |
| let tcx = self.cx.tcx(); |
| let closure_def_id = closure_expr.def_id; |
| // For purposes of this function, coroutine and closures are equivalent. |
| let body_owner_is_closure = matches!( |
| tcx.hir().body_owner_kind(self.cx.body_owner_def_id()), |
| hir::BodyOwnerKind::Closure |
| ); |
| |
| // If we have a nested closure, we want to include the fake reads present in the nested closure. |
| if let Some(fake_reads) = self.cx.typeck_results().closure_fake_reads.get(&closure_def_id) { |
| for (fake_read, cause, hir_id) in fake_reads.iter() { |
| match fake_read.base { |
| PlaceBase::Upvar(upvar_id) => { |
| if upvar_is_local_variable( |
| self.upvars, |
| upvar_id.var_path.hir_id, |
| body_owner_is_closure, |
| ) { |
| // 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; |
| } |
| } |
| _ => { |
| bug!( |
| "Do not know how to get HirId out of Rvalue and StaticItem {:?}", |
| fake_read.base |
| ); |
| } |
| }; |
| self.delegate.borrow_mut().fake_read( |
| &PlaceWithHirId { place: fake_read.clone(), hir_id: *hir_id }, |
| *cause, |
| *hir_id, |
| ); |
| } |
| } |
| |
| if let Some(min_captures) = |
| self.cx.typeck_results().closure_min_captures.get(&closure_def_id) |
| { |
| for (var_hir_id, min_list) in min_captures.iter() { |
| if self |
| .upvars |
| .map_or(body_owner_is_closure, |upvars| !upvars.contains_key(var_hir_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; |
| |
| let place_base = if body_owner_is_closure { |
| // Mark the place to be captured by the enclosing closure |
| PlaceBase::Upvar(ty::UpvarId::new(*var_hir_id, self.cx.body_owner_def_id())) |
| } else { |
| // If the body owner isn't a closure then the variable must |
| // be a local variable |
| PlaceBase::Local(*var_hir_id) |
| }; |
| let closure_hir_id = tcx.local_def_id_to_hir_id(closure_def_id); |
| let place_with_id = PlaceWithHirId::new( |
| capture_info |
| .path_expr_id |
| .unwrap_or(capture_info.capture_kind_expr_id.unwrap_or(closure_hir_id)), |
| place.base_ty, |
| place_base, |
| place.projections.clone(), |
| ); |
| |
| match capture_info.capture_kind { |
| ty::UpvarCapture::ByValue => { |
| self.consume_or_copy(&place_with_id, place_with_id.hir_id); |
| } |
| ty::UpvarCapture::ByRef(upvar_borrow) => { |
| self.delegate.borrow_mut().borrow( |
| &place_with_id, |
| place_with_id.hir_id, |
| upvar_borrow, |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| /// The job of the categorization methods is to analyze an expression to |
| /// determine what kind of memory is used in evaluating it (for example, |
| /// where dereferences occur and what kind of pointer is dereferenced; |
| /// whether the memory is mutable, etc.). |
| /// |
| /// Categorization effectively transforms all of our expressions into |
| /// expressions of the following forms (the actual enum has many more |
| /// possibilities, naturally, but they are all variants of these base |
| /// forms): |
| /// ```ignore (not-rust) |
| /// E = rvalue // some computed rvalue |
| /// | x // address of a local variable or argument |
| /// | *E // deref of a ptr |
| /// | E.comp // access to an interior component |
| /// ``` |
| /// Imagine a routine ToAddr(Expr) that evaluates an expression and returns an |
| /// address where the result is to be found. If Expr is a place, then this |
| /// is the address of the place. If `Expr` is an rvalue, this is the address of |
| /// some temporary spot in memory where the result is stored. |
| /// |
| /// Now, `cat_expr()` classifies the expression `Expr` and the address `A = ToAddr(Expr)` |
| /// as follows: |
| /// |
| /// - `cat`: what kind of expression was this? This is a subset of the |
| /// full expression forms which only includes those that we care about |
| /// for the purpose of the analysis. |
| /// - `mutbl`: mutability of the address `A`. |
| /// - `ty`: the type of data found at the address `A`. |
| /// |
| /// The resulting categorization tree differs somewhat from the expressions |
| /// themselves. 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. |
| /// |
| /// ## By-reference upvars |
| /// |
| /// One part of the codegen which may be non-obvious is that we translate |
| /// closure upvars into the dereference of a borrowed pointer; this more closely |
| /// resembles the runtime codegen. So, for example, if we had: |
| /// |
| /// let mut x = 3; |
| /// let y = 5; |
| /// let inc = || x += y; |
| /// |
| /// Then when we categorize `x` (*within* the closure) we would yield a |
| /// result of `*x'`, effectively, where `x'` is a `Categorization::Upvar` reference |
| /// tied to `x`. The type of `x'` will be a borrowed pointer. |
| impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx, Cx, D> { |
| fn resolve_type_vars_or_error( |
| &self, |
| id: HirId, |
| ty: Option<Ty<'tcx>>, |
| ) -> Result<Ty<'tcx>, Cx::Error> { |
| match ty { |
| Some(ty) => { |
| let ty = self.cx.resolve_vars_if_possible(ty); |
| self.cx.error_reported_in_ty(ty)?; |
| if ty.is_ty_var() { |
| debug!("resolve_type_vars_or_error: infer var from {:?}", ty); |
| Err(self |
| .cx |
| .report_error(self.cx.tcx().hir().span(id), "encountered type variable")) |
| } else { |
| Ok(ty) |
| } |
| } |
| None => { |
| // FIXME: We shouldn't be relying on the infcx being tainted. |
| self.cx.tainted_by_errors()?; |
| bug!( |
| "no type for node {} in mem_categorization", |
| self.cx.tcx().hir().node_to_string(id) |
| ); |
| } |
| } |
| } |
| |
| fn node_ty(&self, hir_id: HirId) -> Result<Ty<'tcx>, Cx::Error> { |
| self.resolve_type_vars_or_error(hir_id, self.cx.typeck_results().node_type_opt(hir_id)) |
| } |
| |
| fn expr_ty(&self, expr: &hir::Expr<'_>) -> Result<Ty<'tcx>, Cx::Error> { |
| self.resolve_type_vars_or_error(expr.hir_id, self.cx.typeck_results().expr_ty_opt(expr)) |
| } |
| |
| fn expr_ty_adjusted(&self, expr: &hir::Expr<'_>) -> Result<Ty<'tcx>, Cx::Error> { |
| self.resolve_type_vars_or_error( |
| expr.hir_id, |
| self.cx.typeck_results().expr_ty_adjusted_opt(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(&self, pat: &hir::Pat<'_>) -> Result<Ty<'tcx>, Cx::Error> { |
| // 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.typeck_results().pat_adjustments().get(pat.hir_id) { |
| if let Some(first_ty) = vec.first() { |
| debug!("pat_ty(pat={:?}) found adjusted ty `{:?}`", pat, first_ty); |
| return Ok(*first_ty); |
| } |
| } else if let PatKind::Ref(subpat, _) = pat.kind |
| && self.cx.typeck_results().skipped_ref_pats().contains(pat.hir_id) |
| { |
| return self.pat_ty_adjusted(subpat); |
| } |
| |
| self.pat_ty_unadjusted(pat) |
| } |
| |
| /// Like `TypeckResults::pat_ty`, but ignores implicit `&` patterns. |
| fn pat_ty_unadjusted(&self, pat: &hir::Pat<'_>) -> Result<Ty<'tcx>, Cx::Error> { |
| let base_ty = self.node_ty(pat.hir_id)?; |
| trace!(?base_ty); |
| |
| // This code detects whether we are looking at a `ref x`, |
| // and if so, figures out what the type *being borrowed* is. |
| match pat.kind { |
| PatKind::Binding(..) => { |
| let bm = *self |
| .cx |
| .typeck_results() |
| .pat_binding_modes() |
| .get(pat.hir_id) |
| .expect("missing binding mode"); |
| |
| if matches!(bm.0, hir::ByRef::Yes(_)) { |
| // a bind-by-ref means that the base_ty will be the type of the ident itself, |
| // but what we want here is the type of the underlying value being borrowed. |
| // So peel off one-level, turning the &T into T. |
| match self |
| .cx |
| .try_structurally_resolve_type(pat.span, base_ty) |
| .builtin_deref(false) |
| { |
| Some(ty) => Ok(ty), |
| None => { |
| debug!("By-ref binding of non-derefable type"); |
| Err(self |
| .cx |
| .report_error(pat.span, "by-ref binding of non-derefable type")) |
| } |
| } |
| } else { |
| Ok(base_ty) |
| } |
| } |
| _ => Ok(base_ty), |
| } |
| } |
| |
| fn cat_expr(&self, expr: &hir::Expr<'_>) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| self.cat_expr_(expr, self.cx.typeck_results().expr_adjustments(expr)) |
| } |
| |
| /// This recursion helper avoids going through *too many* |
| /// adjustments, since *only* non-overloaded deref recurses. |
| fn cat_expr_( |
| &self, |
| expr: &hir::Expr<'_>, |
| adjustments: &[adjustment::Adjustment<'tcx>], |
| ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| match adjustments.split_last() { |
| None => self.cat_expr_unadjusted(expr), |
| Some((adjustment, previous)) => { |
| self.cat_expr_adjusted_with(expr, || self.cat_expr_(expr, previous), adjustment) |
| } |
| } |
| } |
| |
| fn cat_expr_adjusted( |
| &self, |
| expr: &hir::Expr<'_>, |
| previous: PlaceWithHirId<'tcx>, |
| adjustment: &adjustment::Adjustment<'tcx>, |
| ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| self.cat_expr_adjusted_with(expr, || Ok(previous), adjustment) |
| } |
| |
| fn cat_expr_adjusted_with<F>( |
| &self, |
| expr: &hir::Expr<'_>, |
| previous: F, |
| adjustment: &adjustment::Adjustment<'tcx>, |
| ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> |
| where |
| F: FnOnce() -> Result<PlaceWithHirId<'tcx>, Cx::Error>, |
| { |
| let target = self.cx.resolve_vars_if_possible(adjustment.target); |
| match adjustment.kind { |
| adjustment::Adjust::Deref(overloaded) => { |
| // Equivalent to *expr or something similar. |
| let base = if let Some(deref) = overloaded { |
| let ref_ty = Ty::new_ref(self.cx.tcx(), deref.region, target, deref.mutbl); |
| self.cat_rvalue(expr.hir_id, ref_ty) |
| } else { |
| previous()? |
| }; |
| self.cat_deref(expr.hir_id, base) |
| } |
| |
| adjustment::Adjust::NeverToAny |
| | adjustment::Adjust::Pointer(_) |
| | adjustment::Adjust::Borrow(_) |
| | adjustment::Adjust::DynStar => { |
| // Result is an rvalue. |
| Ok(self.cat_rvalue(expr.hir_id, target)) |
| } |
| } |
| } |
| |
| fn cat_expr_unadjusted(&self, expr: &hir::Expr<'_>) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| let expr_ty = self.expr_ty(expr)?; |
| match expr.kind { |
| hir::ExprKind::Unary(hir::UnOp::Deref, e_base) => { |
| if self.cx.typeck_results().is_method_call(expr) { |
| self.cat_overloaded_place(expr, e_base) |
| } else { |
| let base = self.cat_expr(e_base)?; |
| self.cat_deref(expr.hir_id, base) |
| } |
| } |
| |
| hir::ExprKind::Field(base, _) => { |
| let base = self.cat_expr(base)?; |
| debug!(?base); |
| |
| let field_idx = self |
| .cx |
| .typeck_results() |
| .field_indices() |
| .get(expr.hir_id) |
| .cloned() |
| .expect("Field index not found"); |
| |
| Ok(self.cat_projection( |
| expr.hir_id, |
| base, |
| expr_ty, |
| ProjectionKind::Field(field_idx, FIRST_VARIANT), |
| )) |
| } |
| |
| hir::ExprKind::Index(base, _, _) => { |
| if self.cx.typeck_results().is_method_call(expr) { |
| // If this is an index implemented by a method call, then it |
| // will include an implicit deref of the result. |
| // The call to index() returns a `&T` value, which |
| // is an rvalue. That is what we will be |
| // dereferencing. |
| self.cat_overloaded_place(expr, base) |
| } else { |
| let base = self.cat_expr(base)?; |
| Ok(self.cat_projection(expr.hir_id, base, expr_ty, ProjectionKind::Index)) |
| } |
| } |
| |
| hir::ExprKind::Path(ref qpath) => { |
| let res = self.cx.typeck_results().qpath_res(qpath, expr.hir_id); |
| self.cat_res(expr.hir_id, expr.span, expr_ty, res) |
| } |
| |
| hir::ExprKind::Type(e, _) => self.cat_expr(e), |
| |
| hir::ExprKind::AddrOf(..) |
| | hir::ExprKind::Call(..) |
| | hir::ExprKind::Assign(..) |
| | hir::ExprKind::AssignOp(..) |
| | hir::ExprKind::Closure { .. } |
| | hir::ExprKind::Ret(..) |
| | hir::ExprKind::Become(..) |
| | hir::ExprKind::Unary(..) |
| | hir::ExprKind::Yield(..) |
| | hir::ExprKind::MethodCall(..) |
| | hir::ExprKind::Cast(..) |
| | hir::ExprKind::DropTemps(..) |
| | hir::ExprKind::Array(..) |
| | hir::ExprKind::If(..) |
| | hir::ExprKind::Tup(..) |
| | hir::ExprKind::Binary(..) |
| | hir::ExprKind::Block(..) |
| | hir::ExprKind::Let(..) |
| | hir::ExprKind::Loop(..) |
| | hir::ExprKind::Match(..) |
| | hir::ExprKind::Lit(..) |
| | hir::ExprKind::ConstBlock(..) |
| | hir::ExprKind::Break(..) |
| | hir::ExprKind::Continue(..) |
| | hir::ExprKind::Struct(..) |
| | hir::ExprKind::Repeat(..) |
| | hir::ExprKind::InlineAsm(..) |
| | hir::ExprKind::OffsetOf(..) |
| | hir::ExprKind::Err(_) => Ok(self.cat_rvalue(expr.hir_id, expr_ty)), |
| } |
| } |
| |
| fn cat_res( |
| &self, |
| hir_id: HirId, |
| span: Span, |
| expr_ty: Ty<'tcx>, |
| res: Res, |
| ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| match res { |
| Res::Def( |
| DefKind::Ctor(..) |
| | DefKind::Const |
| | DefKind::ConstParam |
| | DefKind::AssocConst |
| | DefKind::Fn |
| | DefKind::AssocFn, |
| _, |
| ) |
| | Res::SelfCtor(..) => Ok(self.cat_rvalue(hir_id, expr_ty)), |
| |
| Res::Def(DefKind::Static { .. }, _) => { |
| Ok(PlaceWithHirId::new(hir_id, expr_ty, PlaceBase::StaticItem, Vec::new())) |
| } |
| |
| Res::Local(var_id) => { |
| if self.upvars.is_some_and(|upvars| upvars.contains_key(&var_id)) { |
| self.cat_upvar(hir_id, var_id) |
| } else { |
| Ok(PlaceWithHirId::new(hir_id, expr_ty, PlaceBase::Local(var_id), Vec::new())) |
| } |
| } |
| |
| def => span_bug!(span, "unexpected definition in memory categorization: {:?}", def), |
| } |
| } |
| |
| /// 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(&self, hir_id: HirId, var_id: HirId) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| let closure_expr_def_id = self.cx.body_owner_def_id(); |
| |
| let upvar_id = ty::UpvarId { |
| var_path: ty::UpvarPath { hir_id: var_id }, |
| closure_expr_id: closure_expr_def_id, |
| }; |
| let var_ty = self.node_ty(var_id)?; |
| |
| Ok(PlaceWithHirId::new(hir_id, var_ty, PlaceBase::Upvar(upvar_id), Vec::new())) |
| } |
| |
| fn cat_rvalue(&self, hir_id: HirId, expr_ty: Ty<'tcx>) -> PlaceWithHirId<'tcx> { |
| PlaceWithHirId::new(hir_id, expr_ty, PlaceBase::Rvalue, Vec::new()) |
| } |
| |
| fn cat_projection( |
| &self, |
| node: HirId, |
| base_place: PlaceWithHirId<'tcx>, |
| ty: Ty<'tcx>, |
| kind: ProjectionKind, |
| ) -> PlaceWithHirId<'tcx> { |
| let place_ty = base_place.place.ty(); |
| let mut projections = base_place.place.projections; |
| |
| let node_ty = self.cx.typeck_results().node_type(node); |
| // Opaque types can't have field projections, but we can instead convert |
| // the current place in-place (heh) to the hidden type, and then apply all |
| // follow up projections on that. |
| if node_ty != place_ty |
| && self |
| .cx |
| .try_structurally_resolve_type( |
| self.cx.tcx().hir().span(base_place.hir_id), |
| place_ty, |
| ) |
| .is_impl_trait() |
| { |
| projections.push(Projection { kind: ProjectionKind::OpaqueCast, ty: node_ty }); |
| } |
| projections.push(Projection { kind, ty }); |
| PlaceWithHirId::new(node, base_place.place.base_ty, base_place.place.base, projections) |
| } |
| |
| fn cat_overloaded_place( |
| &self, |
| expr: &hir::Expr<'_>, |
| base: &hir::Expr<'_>, |
| ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| // 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 ty::Ref(region, _, mutbl) = |
| *self.cx.try_structurally_resolve_type(base.span, base_ty).kind() |
| else { |
| span_bug!(expr.span, "cat_overloaded_place: base is not a reference"); |
| }; |
| let ref_ty = Ty::new_ref(self.cx.tcx(), region, place_ty, mutbl); |
| |
| let base = self.cat_rvalue(expr.hir_id, ref_ty); |
| self.cat_deref(expr.hir_id, base) |
| } |
| |
| fn cat_deref( |
| &self, |
| node: HirId, |
| base_place: PlaceWithHirId<'tcx>, |
| ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> { |
| let base_curr_ty = base_place.place.ty(); |
| let deref_ty = match self |
| .cx |
| .try_structurally_resolve_type( |
| self.cx.tcx().hir().span(base_place.hir_id), |
| base_curr_ty, |
| ) |
| .builtin_deref(true) |
| { |
| Some(ty) => ty, |
| None => { |
| debug!("explicit deref of non-derefable type: {:?}", base_curr_ty); |
| return Err(self.cx.report_error( |
| self.cx.tcx().hir().span(node), |
| "explicit deref of non-derefable type", |
| )); |
| } |
| }; |
| let mut projections = base_place.place.projections; |
| projections.push(Projection { kind: ProjectionKind::Deref, ty: deref_ty }); |
| |
| Ok(PlaceWithHirId::new(node, base_place.place.base_ty, base_place.place.base, projections)) |
| } |
| |
| /// Returns the variant index for an ADT used within a Struct or TupleStruct pattern |
| /// Here `pat_hir_id` is the HirId of the pattern itself. |
| fn variant_index_for_adt( |
| &self, |
| qpath: &hir::QPath<'_>, |
| pat_hir_id: HirId, |
| span: Span, |
| ) -> Result<VariantIdx, Cx::Error> { |
| let res = self.cx.typeck_results().qpath_res(qpath, pat_hir_id); |
| let ty = self.cx.typeck_results().node_type(pat_hir_id); |
| let ty::Adt(adt_def, _) = self.cx.try_structurally_resolve_type(span, ty).kind() else { |
| return Err(self |
| .cx |
| .report_error(span, "struct or tuple struct pattern not applied to an ADT")); |
| }; |
| |
| match res { |
| Res::Def(DefKind::Variant, variant_id) => Ok(adt_def.variant_index_with_id(variant_id)), |
| Res::Def(DefKind::Ctor(CtorOf::Variant, ..), variant_ctor_id) => { |
| Ok(adt_def.variant_index_with_ctor_id(variant_ctor_id)) |
| } |
| Res::Def(DefKind::Ctor(CtorOf::Struct, ..), _) |
| | Res::Def(DefKind::Struct | DefKind::Union | DefKind::TyAlias | DefKind::AssocTy, _) |
| | Res::SelfCtor(..) |
| | Res::SelfTyParam { .. } |
| | Res::SelfTyAlias { .. } => { |
| // Structs and Unions have only have one variant. |
| Ok(FIRST_VARIANT) |
| } |
| _ => bug!("expected ADT path, found={:?}", res), |
| } |
| } |
| |
| /// Returns the total number of fields in an ADT variant used within a pattern. |
| /// Here `pat_hir_id` is the HirId of the pattern itself. |
| fn total_fields_in_adt_variant( |
| &self, |
| pat_hir_id: HirId, |
| variant_index: VariantIdx, |
| span: Span, |
| ) -> Result<usize, Cx::Error> { |
| let ty = self.cx.typeck_results().node_type(pat_hir_id); |
| match self.cx.try_structurally_resolve_type(span, ty).kind() { |
| ty::Adt(adt_def, _) => Ok(adt_def.variant(variant_index).fields.len()), |
| _ => { |
| self.cx |
| .tcx() |
| .dcx() |
| .span_bug(span, "struct or tuple struct pattern not applied to an ADT"); |
| } |
| } |
| } |
| |
| /// Returns the total number of fields in a tuple used within a Tuple pattern. |
| /// Here `pat_hir_id` is the HirId of the pattern itself. |
| fn total_fields_in_tuple(&self, pat_hir_id: HirId, span: Span) -> Result<usize, Cx::Error> { |
| let ty = self.cx.typeck_results().node_type(pat_hir_id); |
| match self.cx.try_structurally_resolve_type(span, ty).kind() { |
| ty::Tuple(args) => Ok(args.len()), |
| _ => Err(self.cx.report_error(span, "tuple pattern not applied to a tuple")), |
| } |
| } |
| |
| /// Here, `place` is the `PlaceWithHirId` 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 `PlaceWithHirId` that represents the path that will be taken |
| /// to reach the value being matched. |
| fn cat_pattern<F>( |
| &self, |
| mut place_with_id: PlaceWithHirId<'tcx>, |
| pat: &hir::Pat<'_>, |
| op: &mut F, |
| ) -> Result<(), Cx::Error> |
| where |
| F: FnMut(&PlaceWithHirId<'tcx>, &hir::Pat<'_>) -> Result<(), Cx::Error>, |
| { |
| // If (pattern) adjustments are active for this pattern, adjust the `PlaceWithHirId` correspondingly. |
| // `PlaceWithHirId`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 `PlaceWithHirId` we start with the `PlaceWithHirId` 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,)`. |
| for _ in |
| 0..self.cx.typeck_results().pat_adjustments().get(pat.hir_id).map_or(0, |v| v.len()) |
| { |
| debug!("applying adjustment to place_with_id={:?}", place_with_id); |
| place_with_id = self.cat_deref(pat.hir_id, 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(&place_with_id, pat)?; |
| |
| match pat.kind { |
| PatKind::Tuple(subpats, dots_pos) => { |
| // (p1, ..., pN) |
| let total_fields = self.total_fields_in_tuple(pat.hir_id, pat.span)?; |
| |
| 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(FieldIdx::from_usize(i), FIRST_VARIANT); |
| let sub_place = self.cat_projection( |
| pat.hir_id, |
| place_with_id.clone(), |
| subpat_ty, |
| projection_kind, |
| ); |
| self.cat_pattern(sub_place, subpat, op)?; |
| } |
| } |
| |
| PatKind::TupleStruct(ref qpath, subpats, dots_pos) => { |
| // S(p1, ..., pN) |
| let variant_index = self.variant_index_for_adt(qpath, pat.hir_id, pat.span)?; |
| let total_fields = |
| self.total_fields_in_adt_variant(pat.hir_id, variant_index, pat.span)?; |
| |
| 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(FieldIdx::from_usize(i), variant_index); |
| let sub_place = self.cat_projection( |
| pat.hir_id, |
| place_with_id.clone(), |
| subpat_ty, |
| projection_kind, |
| ); |
| self.cat_pattern(sub_place, subpat, op)?; |
| } |
| } |
| |
| PatKind::Struct(ref qpath, field_pats, _) => { |
| // S { f1: p1, ..., fN: pN } |
| |
| let variant_index = self.variant_index_for_adt(qpath, pat.hir_id, pat.span)?; |
| |
| for fp in field_pats { |
| let field_ty = self.pat_ty_adjusted(fp.pat)?; |
| let field_index = self |
| .cx |
| .typeck_results() |
| .field_indices() |
| .get(fp.hir_id) |
| .cloned() |
| .expect("no index for a field"); |
| |
| let field_place = self.cat_projection( |
| pat.hir_id, |
| place_with_id.clone(), |
| field_ty, |
| ProjectionKind::Field(field_index, variant_index), |
| ); |
| self.cat_pattern(field_place, fp.pat, op)?; |
| } |
| } |
| |
| PatKind::Or(pats) => { |
| for pat in pats { |
| self.cat_pattern(place_with_id.clone(), pat, op)?; |
| } |
| } |
| |
| PatKind::Binding(.., Some(subpat)) => { |
| self.cat_pattern(place_with_id, subpat, op)?; |
| } |
| |
| PatKind::Ref(subpat, _) |
| if self.cx.typeck_results().skipped_ref_pats().contains(pat.hir_id) => |
| { |
| self.cat_pattern(place_with_id, subpat, op)?; |
| } |
| |
| PatKind::Box(subpat) | PatKind::Ref(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.hir_id, place_with_id)?; |
| self.cat_pattern(subplace, subpat, op)?; |
| } |
| PatKind::Deref(subpat) => { |
| let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(subpat); |
| let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not }; |
| let re_erased = self.cx.tcx().lifetimes.re_erased; |
| let ty = self.pat_ty_adjusted(subpat)?; |
| let ty = Ty::new_ref(self.cx.tcx(), re_erased, ty, mutability); |
| // A deref pattern generates a temporary. |
| let place = self.cat_rvalue(pat.hir_id, ty); |
| self.cat_pattern(place, subpat, op)?; |
| } |
| |
| PatKind::Slice(before, ref slice, after) => { |
| let Some(element_ty) = place_with_id.place.ty().builtin_index() else { |
| debug!("explicit index of non-indexable type {:?}", place_with_id); |
| return Err(self |
| .cx |
| .report_error(pat.span, "explicit index of non-indexable type")); |
| }; |
| let elt_place = self.cat_projection( |
| pat.hir_id, |
| 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.hir_id, |
| 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)?; |
| } |
| } |
| |
| PatKind::Path(_) |
| | PatKind::Binding(.., None) |
| | PatKind::Lit(..) |
| | PatKind::Range(..) |
| | PatKind::Never |
| | PatKind::Wild |
| | PatKind::Err(_) => { |
| // always ok |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool { |
| if let ty::Adt(def, _) = self.cx.try_structurally_resolve_type(span, 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. |
| // If the variant is not local it must be defined in another crate. |
| let is_non_exhaustive = match def.adt_kind() { |
| AdtKind::Struct | AdtKind::Union => { |
| def.non_enum_variant().is_field_list_non_exhaustive() |
| } |
| AdtKind::Enum => def.is_variant_list_non_exhaustive(), |
| }; |
| def.variants().len() > 1 || (!def.did().is_local() && is_non_exhaustive) |
| } else { |
| false |
| } |
| } |
| } |