// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! # Categorization //! //! The job of the categorization module 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): //! //! 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. #![allow(non_camel_case_types)] pub use self::PointerKind::*; pub use self::InteriorKind::*; pub use self::MutabilityCategory::*; pub use self::AliasableReason::*; pub use self::Note::*; use self::Aliasability::*; use middle::region; use hir::def_id::{DefId, LocalDefId}; use hir::Node; use infer::InferCtxt; use hir::def::{Def, CtorKind}; use ty::adjustment; use ty::{self, Ty, TyCtxt}; use ty::fold::TypeFoldable; use hir::{MutImmutable, MutMutable, PatKind}; use hir::pat_util::EnumerateAndAdjustIterator; use hir; use syntax::ast::{self, Name}; use syntax_pos::Span; use std::borrow::Cow; use std::fmt; use std::hash::{Hash, Hasher}; use rustc_data_structures::sync::Lrc; use std::rc::Rc; use util::nodemap::ItemLocalSet; #[derive(Clone, Debug, PartialEq)] pub enum Categorization<'tcx> { Rvalue(ty::Region<'tcx>), // temporary val, argument is its scope StaticItem, Upvar(Upvar), // upvar referenced by closure env Local(ast::NodeId), // local variable Deref(cmt<'tcx>, PointerKind<'tcx>), // deref of a ptr Interior(cmt<'tcx>, InteriorKind), // something interior: field, tuple, etc Downcast(cmt<'tcx>, DefId), // selects a particular enum variant (*1) // (*1) downcast is only required if the enum has more than one variant } // Represents any kind of upvar #[derive(Clone, Copy, PartialEq)] pub struct Upvar { pub id: ty::UpvarId, pub kind: ty::ClosureKind } // different kinds of pointers: #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum PointerKind<'tcx> { /// `Box` Unique, /// `&T` BorrowedPtr(ty::BorrowKind, ty::Region<'tcx>), /// `*T` UnsafePtr(hir::Mutability), } // We use the term "interior" to mean "something reachable from the // base without a pointer dereference", e.g. a field #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum InteriorKind { InteriorField(FieldIndex), InteriorElement(InteriorOffsetKind), } // Contains index of a field that is actually used for loan path comparisons and // string representation of the field that should be used only for diagnostics. #[derive(Clone, Copy, Eq)] pub struct FieldIndex(pub usize, pub Name); impl PartialEq for FieldIndex { fn eq(&self, rhs: &Self) -> bool { self.0 == rhs.0 } } impl Hash for FieldIndex { fn hash(&self, h: &mut H) { self.0.hash(h) } } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum InteriorOffsetKind { Index, // e.g. `array_expr[index_expr]` Pattern, // e.g. `fn foo([_, a, _, _]: [A; 4]) { ... }` } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum MutabilityCategory { McImmutable, // Immutable. McDeclared, // Directly declared as mutable. McInherited, // Inherited from the fact that owner is mutable. } // A note about the provenance of a `cmt`. This is used for // special-case handling of upvars such as mutability inference. // Upvar categorization can generate a variable number of nested // derefs. The note allows detecting them without deep pattern // matching on the categorization. #[derive(Clone, Copy, PartialEq, Debug)] pub enum Note { NoteClosureEnv(ty::UpvarId), // Deref through closure env NoteUpvarRef(ty::UpvarId), // Deref through by-ref upvar NoteIndex, // Deref as part of desugaring `x[]` into its two components NoteNone // Nothing special } // `cmt`: "Category, Mutability, and Type". // // a complete categorization of a value indicating where it originated // and how it is located, as well as the mutability of the memory in // which the value is stored. // // *WARNING* The field `cmt.type` is NOT necessarily the same as the // result of `node_id_to_type(cmt.id)`. // // (FIXME: rewrite the following comment given that `@x` managed // pointers have been obsolete for quite some time.) // // This is because the `id` is always the `id` of the node producing the // type; in an expression like `*x`, the type of this deref node is the // deref'd type (`T`), but in a pattern like `@x`, the `@x` pattern is // again a dereference, but its type is the type *before* the // dereference (`@T`). So use `cmt.ty` to find the type of the value in // a consistent fashion. For more details, see the method `cat_pattern` #[derive(Clone, Debug, PartialEq)] pub struct cmt_<'tcx> { pub hir_id: hir::HirId, // HIR id of expr/pat producing this value pub span: Span, // span of same expr/pat pub cat: Categorization<'tcx>, // categorization of expr pub mutbl: MutabilityCategory, // mutability of expr as place pub ty: Ty<'tcx>, // type of the expr (*see WARNING above*) pub note: Note, // Note about the provenance of this cmt } pub type cmt<'tcx> = Rc>; pub enum ImmutabilityBlame<'tcx> { ImmLocal(ast::NodeId), ClosureEnv(LocalDefId), LocalDeref(ast::NodeId), AdtFieldDeref(&'tcx ty::AdtDef, &'tcx ty::FieldDef) } impl<'tcx> cmt_<'tcx> { fn resolve_field(&self, field_index: usize) -> Option<(&'tcx ty::AdtDef, &'tcx ty::FieldDef)> { let adt_def = match self.ty.sty { ty::Adt(def, _) => def, ty::Tuple(..) => return None, // closures get `Categorization::Upvar` rather than `Categorization::Interior` _ => bug!("interior cmt {:?} is not an ADT", self) }; let variant_def = match self.cat { Categorization::Downcast(_, variant_did) => { adt_def.variant_with_id(variant_did) } _ => { assert_eq!(adt_def.variants.len(), 1); &adt_def.variants[0] } }; Some((adt_def, &variant_def.fields[field_index])) } pub fn immutability_blame(&self) -> Option> { match self.cat { Categorization::Deref(ref base_cmt, BorrowedPtr(ty::ImmBorrow, _)) => { // try to figure out where the immutable reference came from match base_cmt.cat { Categorization::Local(node_id) => Some(ImmutabilityBlame::LocalDeref(node_id)), Categorization::Interior(ref base_cmt, InteriorField(field_index)) => { base_cmt.resolve_field(field_index.0).map(|(adt_def, field_def)| { ImmutabilityBlame::AdtFieldDeref(adt_def, field_def) }) } Categorization::Upvar(Upvar { id, .. }) => { if let NoteClosureEnv(..) = self.note { Some(ImmutabilityBlame::ClosureEnv(id.closure_expr_id)) } else { None } } _ => None } } Categorization::Local(node_id) => { Some(ImmutabilityBlame::ImmLocal(node_id)) } Categorization::Rvalue(..) | Categorization::Upvar(..) | Categorization::Deref(_, UnsafePtr(..)) => { // This should not be reachable up to inference limitations. None } Categorization::Interior(ref base_cmt, _) | Categorization::Downcast(ref base_cmt, _) | Categorization::Deref(ref base_cmt, _) => { base_cmt.immutability_blame() } Categorization::StaticItem => { // Do we want to do something here? None } } } } pub trait HirNode { fn hir_id(&self) -> hir::HirId; fn span(&self) -> Span; } impl HirNode for hir::Expr { fn hir_id(&self) -> hir::HirId { self.hir_id } fn span(&self) -> Span { self.span } } impl HirNode for hir::Pat { fn hir_id(&self) -> hir::HirId { self.hir_id } fn span(&self) -> Span { self.span } } #[derive(Clone)] pub struct MemCategorizationContext<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { pub tcx: TyCtxt<'a, 'gcx, 'tcx>, pub region_scope_tree: &'a region::ScopeTree, pub tables: &'a ty::TypeckTables<'tcx>, rvalue_promotable_map: Option>, infcx: Option<&'a InferCtxt<'a, 'gcx, 'tcx>>, } pub type McResult = Result; impl MutabilityCategory { pub fn from_mutbl(m: hir::Mutability) -> MutabilityCategory { let ret = match m { MutImmutable => McImmutable, MutMutable => McDeclared }; debug!("MutabilityCategory::{}({:?}) => {:?}", "from_mutbl", m, ret); ret } pub fn from_borrow_kind(borrow_kind: ty::BorrowKind) -> MutabilityCategory { let ret = match borrow_kind { ty::ImmBorrow => McImmutable, ty::UniqueImmBorrow => McImmutable, ty::MutBorrow => McDeclared, }; debug!("MutabilityCategory::{}({:?}) => {:?}", "from_borrow_kind", borrow_kind, ret); ret } fn from_pointer_kind(base_mutbl: MutabilityCategory, ptr: PointerKind<'_>) -> MutabilityCategory { let ret = match ptr { Unique => { base_mutbl.inherit() } BorrowedPtr(borrow_kind, _) => { MutabilityCategory::from_borrow_kind(borrow_kind) } UnsafePtr(m) => { MutabilityCategory::from_mutbl(m) } }; debug!("MutabilityCategory::{}({:?}, {:?}) => {:?}", "from_pointer_kind", base_mutbl, ptr, ret); ret } fn from_local(tcx: TyCtxt<'_, '_, '_>, tables: &ty::TypeckTables<'_>, id: ast::NodeId) -> MutabilityCategory { let ret = match tcx.hir.get(id) { Node::Binding(p) => match p.node { PatKind::Binding(..) => { let bm = *tables.pat_binding_modes() .get(p.hir_id) .expect("missing binding mode"); if bm == ty::BindByValue(hir::MutMutable) { McDeclared } else { McImmutable } } _ => span_bug!(p.span, "expected identifier pattern") }, _ => span_bug!(tcx.hir.span(id), "expected identifier pattern") }; debug!("MutabilityCategory::{}(tcx, id={:?}) => {:?}", "from_local", id, ret); ret } pub fn inherit(&self) -> MutabilityCategory { let ret = match *self { McImmutable => McImmutable, McDeclared => McInherited, McInherited => McInherited, }; debug!("{:?}.inherit() => {:?}", self, ret); ret } pub fn is_mutable(&self) -> bool { let ret = match *self { McImmutable => false, McInherited => true, McDeclared => true, }; debug!("{:?}.is_mutable() => {:?}", self, ret); ret } pub fn is_immutable(&self) -> bool { let ret = match *self { McImmutable => true, McDeclared | McInherited => false }; debug!("{:?}.is_immutable() => {:?}", self, ret); ret } pub fn to_user_str(&self) -> &'static str { match *self { McDeclared | McInherited => "mutable", McImmutable => "immutable", } } } impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, region_scope_tree: &'a region::ScopeTree, tables: &'a ty::TypeckTables<'tcx>, rvalue_promotable_map: Option>) -> MemCategorizationContext<'a, 'tcx, 'tcx> { MemCategorizationContext { tcx, region_scope_tree, tables, rvalue_promotable_map, infcx: None } } } impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> { /// Creates a `MemCategorizationContext` during type inference. /// This is used during upvar analysis and a few other places. /// Because the typeck tables are not yet complete, the results /// from the analysis must be used with caution: /// /// - rvalue promotions are not known, so the lifetimes of /// temporaries may be overly conservative; /// - similarly, as the results of upvar analysis are not yet /// known, the results around upvar accesses may be incorrect. pub fn with_infer(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, region_scope_tree: &'a region::ScopeTree, tables: &'a ty::TypeckTables<'tcx>) -> MemCategorizationContext<'a, 'gcx, 'tcx> { let tcx = infcx.tcx; // Subtle: we can't do rvalue promotion analysis until the // typeck phase is complete, which means that you can't trust // the rvalue lifetimes that result, but that's ok, since we // don't need to know those during type inference. let rvalue_promotable_map = None; MemCategorizationContext { tcx, region_scope_tree, tables, rvalue_promotable_map, infcx: Some(infcx), } } pub fn type_moves_by_default(&self, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>, span: Span) -> bool { self.infcx.map(|infcx| infcx.type_moves_by_default(param_env, ty, span)) .or_else(|| { self.tcx.lift_to_global(&(param_env, ty)).map(|(param_env, ty)| { ty.moves_by_default(self.tcx.global_tcx(), param_env, span) }) }) .unwrap_or(true) } fn resolve_type_vars_if_possible(&self, value: &T) -> T where T: TypeFoldable<'tcx> { self.infcx.map(|infcx| infcx.resolve_type_vars_if_possible(value)) .unwrap_or_else(|| value.clone()) } fn is_tainted_by_errors(&self) -> bool { self.infcx.map_or(false, |infcx| infcx.is_tainted_by_errors()) } fn resolve_type_vars_or_error(&self, id: hir::HirId, ty: Option>) -> McResult> { match ty { Some(ty) => { let ty = self.resolve_type_vars_if_possible(&ty); if ty.references_error() || ty.is_ty_var() { debug!("resolve_type_vars_or_error: error from {:?}", ty); Err(()) } else { Ok(ty) } } // FIXME None if self.is_tainted_by_errors() => Err(()), None => { let id = self.tcx.hir.hir_to_node_id(id); bug!("no type for node {}: {} in mem_categorization", id, self.tcx.hir.node_to_string(id)); } } } pub fn node_ty(&self, hir_id: hir::HirId) -> McResult> { self.resolve_type_vars_or_error(hir_id, self.tables.node_id_to_type_opt(hir_id)) } pub fn expr_ty(&self, expr: &hir::Expr) -> McResult> { self.resolve_type_vars_or_error(expr.hir_id, self.tables.expr_ty_opt(expr)) } pub fn expr_ty_adjusted(&self, expr: &hir::Expr) -> McResult> { self.resolve_type_vars_or_error(expr.hir_id, self.tables.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). pub fn pat_ty_adjusted(&self, pat: &hir::Pat) -> McResult> { // 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.tables.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); } } self.pat_ty_unadjusted(pat) } /// Like `pat_ty`, but ignores implicit `&` patterns. fn pat_ty_unadjusted(&self, pat: &hir::Pat) -> McResult> { let base_ty = self.node_ty(pat.hir_id)?; debug!("pat_ty(pat={:?}) base_ty={:?}", pat, base_ty); // This code detects whether we are looking at a `ref x`, // and if so, figures out what the type *being borrowed* is. let ret_ty = match pat.node { PatKind::Binding(..) => { let bm = *self.tables .pat_binding_modes() .get(pat.hir_id) .expect("missing binding mode"); if let ty::BindByReference(_) = bm { // 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 base_ty.builtin_deref(false) { Some(t) => t.ty, None => { debug!("By-ref binding of non-derefable type {:?}", base_ty); return Err(()); } } } else { base_ty } } _ => base_ty, }; debug!("pat_ty(pat={:?}) ret_ty={:?}", pat, ret_ty); Ok(ret_ty) } pub fn cat_expr(&self, expr: &hir::Expr) -> McResult> { // This recursion helper avoids going through *too many* // adjustments, since *only* non-overloaded deref recurses. fn helper<'a, 'gcx, 'tcx>(mc: &MemCategorizationContext<'a, 'gcx, 'tcx>, expr: &hir::Expr, adjustments: &[adjustment::Adjustment<'tcx>]) -> McResult> { match adjustments.split_last() { None => mc.cat_expr_unadjusted(expr), Some((adjustment, previous)) => { mc.cat_expr_adjusted_with(expr, || helper(mc, expr, previous), adjustment) } } } helper(self, expr, self.tables.expr_adjustments(expr)) } pub fn cat_expr_adjusted(&self, expr: &hir::Expr, previous: cmt_<'tcx>, adjustment: &adjustment::Adjustment<'tcx>) -> McResult> { self.cat_expr_adjusted_with(expr, || Ok(previous), adjustment) } fn cat_expr_adjusted_with(&self, expr: &hir::Expr, previous: F, adjustment: &adjustment::Adjustment<'tcx>) -> McResult> where F: FnOnce() -> McResult> { debug!("cat_expr_adjusted_with({:?}): {:?}", adjustment, expr); let target = self.resolve_type_vars_if_possible(&adjustment.target); match adjustment.kind { adjustment::Adjust::Deref(overloaded) => { // Equivalent to *expr or something similar. let base = Rc::new(if let Some(deref) = overloaded { let ref_ty = self.tcx.mk_ref(deref.region, ty::TypeAndMut { ty: target, mutbl: deref.mutbl, }); self.cat_rvalue_node(expr.hir_id, expr.span, ref_ty) } else { previous()? }); self.cat_deref(expr, base, NoteNone) } adjustment::Adjust::NeverToAny | adjustment::Adjust::ReifyFnPointer | adjustment::Adjust::UnsafeFnPointer | adjustment::Adjust::ClosureFnPointer | adjustment::Adjust::MutToConstPointer | adjustment::Adjust::Borrow(_) | adjustment::Adjust::Unsize => { // Result is an rvalue. Ok(self.cat_rvalue_node(expr.hir_id, expr.span, target)) } } } pub fn cat_expr_unadjusted(&self, expr: &hir::Expr) -> McResult> { debug!("cat_expr: id={} expr={:?}", expr.id, expr); let expr_ty = self.expr_ty(expr)?; match expr.node { hir::ExprKind::Unary(hir::UnDeref, ref e_base) => { if self.tables.is_method_call(expr) { self.cat_overloaded_place(expr, e_base, NoteNone) } else { let base_cmt = Rc::new(self.cat_expr(&e_base)?); self.cat_deref(expr, base_cmt, NoteNone) } } hir::ExprKind::Field(ref base, f_ident) => { let base_cmt = Rc::new(self.cat_expr(&base)?); debug!("cat_expr(cat_field): id={} expr={:?} base={:?}", expr.id, expr, base_cmt); let f_index = self.tcx.field_index(expr.id, self.tables); Ok(self.cat_field(expr, base_cmt, f_index, f_ident, expr_ty)) } hir::ExprKind::Index(ref base, _) => { if self.tables.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, NoteIndex) } else { let base_cmt = Rc::new(self.cat_expr(&base)?); self.cat_index(expr, base_cmt, expr_ty, InteriorOffsetKind::Index) } } hir::ExprKind::Path(ref qpath) => { let def = self.tables.qpath_def(qpath, expr.hir_id); self.cat_def(expr.hir_id, expr.span, expr_ty, def) } hir::ExprKind::Type(ref 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::Unary(..) | hir::ExprKind::Yield(..) | hir::ExprKind::MethodCall(..) | hir::ExprKind::Cast(..) | hir::ExprKind::Array(..) | hir::ExprKind::Tup(..) | hir::ExprKind::If(..) | hir::ExprKind::Binary(..) | hir::ExprKind::While(..) | hir::ExprKind::Block(..) | hir::ExprKind::Loop(..) | hir::ExprKind::Match(..) | hir::ExprKind::Lit(..) | hir::ExprKind::Break(..) | hir::ExprKind::Continue(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) | hir::ExprKind::InlineAsm(..) | hir::ExprKind::Box(..) => { Ok(self.cat_rvalue_node(expr.hir_id, expr.span, expr_ty)) } } } pub fn cat_def(&self, hir_id: hir::HirId, span: Span, expr_ty: Ty<'tcx>, def: Def) -> McResult> { debug!("cat_def: id={:?} expr={:?} def={:?}", hir_id, expr_ty, def); match def { Def::StructCtor(..) | Def::VariantCtor(..) | Def::Const(..) | Def::AssociatedConst(..) | Def::Fn(..) | Def::Method(..) | Def::SelfCtor(..) => { Ok(self.cat_rvalue_node(hir_id, span, expr_ty)) } Def::Static(def_id, mutbl) => { // `#[thread_local]` statics may not outlive the current function. for attr in &self.tcx.get_attrs(def_id)[..] { if attr.check_name("thread_local") { return Ok(self.cat_rvalue_node(hir_id, span, expr_ty)); } } Ok(cmt_ { hir_id, span:span, cat:Categorization::StaticItem, mutbl: if mutbl { McDeclared } else { McImmutable}, ty:expr_ty, note: NoteNone }) } Def::Upvar(var_id, _, fn_node_id) => { self.cat_upvar(hir_id, span, var_id, fn_node_id) } Def::Local(vid) => { Ok(cmt_ { hir_id, span, cat: Categorization::Local(vid), mutbl: MutabilityCategory::from_local(self.tcx, self.tables, vid), ty: expr_ty, note: NoteNone }) } def => span_bug!(span, "unexpected definition in memory categorization: {:?}", def) } } // Categorize an upvar, complete with invisible derefs of closure // environment and upvar reference as appropriate. fn cat_upvar(&self, hir_id: hir::HirId, span: Span, var_id: ast::NodeId, fn_node_id: ast::NodeId) -> McResult> { let fn_hir_id = self.tcx.hir.node_to_hir_id(fn_node_id); // An upvar can have up to 3 components. We translate first to a // `Categorization::Upvar`, which is itself a fiction -- it represents the reference to the // field from the environment. // // `Categorization::Upvar`. Next, we add a deref through the implicit // environment pointer with an anonymous free region 'env and // appropriate borrow kind for closure kinds that take self by // reference. Finally, if the upvar was captured // by-reference, we add a deref through that reference. The // region of this reference is an inference variable 'up that // was previously generated and recorded in the upvar borrow // map. The borrow kind bk is inferred by based on how the // upvar is used. // // This results in the following table for concrete closure // types: // // | move | ref // ---------------+----------------------+------------------------------- // Fn | copied -> &'env | upvar -> &'env -> &'up bk // FnMut | copied -> &'env mut | upvar -> &'env mut -> &'up bk // FnOnce | copied | upvar -> &'up bk let kind = match self.node_ty(fn_hir_id)?.sty { ty::Generator(..) => ty::ClosureKind::FnOnce, ty::Closure(closure_def_id, closure_substs) => { match self.infcx { // During upvar inference we may not know the // closure kind, just use the LATTICE_BOTTOM value. Some(infcx) => infcx.closure_kind(closure_def_id, closure_substs) .unwrap_or(ty::ClosureKind::LATTICE_BOTTOM), None => self.tcx.global_tcx() .lift(&closure_substs) .expect("no inference cx, but inference variables in closure ty") .closure_kind(closure_def_id, self.tcx.global_tcx()), } } ref t => span_bug!(span, "unexpected type for fn in mem_categorization: {:?}", t), }; let closure_expr_def_id = self.tcx.hir.local_def_id(fn_node_id); let var_hir_id = self.tcx.hir.node_to_hir_id(var_id); let upvar_id = ty::UpvarId { var_id: var_hir_id, closure_expr_id: closure_expr_def_id.to_local(), }; let var_ty = self.node_ty(var_hir_id)?; // Mutability of original variable itself let var_mutbl = MutabilityCategory::from_local(self.tcx, self.tables, var_id); // Construct the upvar. This represents access to the field // from the environment (perhaps we should eventually desugar // this field further, but it will do for now). let cmt_result = cmt_ { hir_id, span, cat: Categorization::Upvar(Upvar {id: upvar_id, kind: kind}), mutbl: var_mutbl, ty: var_ty, note: NoteNone }; // If this is a `FnMut` or `Fn` closure, then the above is // conceptually a `&mut` or `&` reference, so we have to add a // deref. let cmt_result = match kind { ty::ClosureKind::FnOnce => { cmt_result } ty::ClosureKind::FnMut => { self.env_deref(hir_id, span, upvar_id, var_mutbl, ty::MutBorrow, cmt_result) } ty::ClosureKind::Fn => { self.env_deref(hir_id, span, upvar_id, var_mutbl, ty::ImmBorrow, cmt_result) } }; // If this is a by-ref capture, then the upvar we loaded is // actually a reference, so we have to add an implicit deref // for that. let upvar_capture = self.tables.upvar_capture(upvar_id); let cmt_result = match upvar_capture { ty::UpvarCapture::ByValue => { cmt_result } ty::UpvarCapture::ByRef(upvar_borrow) => { let ptr = BorrowedPtr(upvar_borrow.kind, upvar_borrow.region); cmt_ { hir_id, span, cat: Categorization::Deref(Rc::new(cmt_result), ptr), mutbl: MutabilityCategory::from_borrow_kind(upvar_borrow.kind), ty: var_ty, note: NoteUpvarRef(upvar_id) } } }; let ret = cmt_result; debug!("cat_upvar ret={:?}", ret); Ok(ret) } fn env_deref(&self, hir_id: hir::HirId, span: Span, upvar_id: ty::UpvarId, upvar_mutbl: MutabilityCategory, env_borrow_kind: ty::BorrowKind, cmt_result: cmt_<'tcx>) -> cmt_<'tcx> { // Region of environment pointer let env_region = self.tcx.mk_region(ty::ReFree(ty::FreeRegion { // The environment of a closure is guaranteed to // outlive any bindings introduced in the body of the // closure itself. scope: upvar_id.closure_expr_id.to_def_id(), bound_region: ty::BrEnv })); let env_ptr = BorrowedPtr(env_borrow_kind, env_region); let var_ty = cmt_result.ty; // We need to add the env deref. This means // that the above is actually immutable and // has a ref type. However, nothing should // actually look at the type, so we can get // away with stuffing a `Error` in there // instead of bothering to construct a proper // one. let cmt_result = cmt_ { mutbl: McImmutable, ty: self.tcx.types.err, ..cmt_result }; let mut deref_mutbl = MutabilityCategory::from_borrow_kind(env_borrow_kind); // Issue #18335. If variable is declared as immutable, override the // mutability from the environment and substitute an `&T` anyway. match upvar_mutbl { McImmutable => { deref_mutbl = McImmutable; } McDeclared | McInherited => { } } let ret = cmt_ { hir_id, span, cat: Categorization::Deref(Rc::new(cmt_result), env_ptr), mutbl: deref_mutbl, ty: var_ty, note: NoteClosureEnv(upvar_id) }; debug!("env_deref ret {:?}", ret); ret } /// Returns the lifetime of a temporary created by expr with id `id`. /// This could be `'static` if `id` is part of a constant expression. pub fn temporary_scope(&self, id: hir::ItemLocalId) -> ty::Region<'tcx> { let scope = self.region_scope_tree.temporary_scope(id); self.tcx.mk_region(match scope { Some(scope) => ty::ReScope(scope), None => ty::ReStatic }) } pub fn cat_rvalue_node(&self, hir_id: hir::HirId, span: Span, expr_ty: Ty<'tcx>) -> cmt_<'tcx> { debug!( "cat_rvalue_node(id={:?}, span={:?}, expr_ty={:?})", hir_id, span, expr_ty, ); let promotable = self.rvalue_promotable_map.as_ref().map(|m| m.contains(&hir_id.local_id)) .unwrap_or(false); debug!( "cat_rvalue_node: promotable = {:?}", promotable, ); // Always promote `[T; 0]` (even when e.g. borrowed mutably). let promotable = match expr_ty.sty { ty::Array(_, len) if len.assert_usize(self.tcx) == Some(0) => true, _ => promotable, }; debug!( "cat_rvalue_node: promotable = {:?} (2)", promotable, ); // Compute maximum lifetime of this rvalue. This is 'static if // we can promote to a constant, otherwise equal to enclosing temp // lifetime. let re = if promotable { self.tcx.types.re_static } else { self.temporary_scope(hir_id.local_id) }; let ret = self.cat_rvalue(hir_id, span, re, expr_ty); debug!("cat_rvalue_node ret {:?}", ret); ret } pub fn cat_rvalue(&self, cmt_hir_id: hir::HirId, span: Span, temp_scope: ty::Region<'tcx>, expr_ty: Ty<'tcx>) -> cmt_<'tcx> { let ret = cmt_ { hir_id: cmt_hir_id, span:span, cat:Categorization::Rvalue(temp_scope), mutbl:McDeclared, ty:expr_ty, note: NoteNone }; debug!("cat_rvalue ret {:?}", ret); ret } pub fn cat_field(&self, node: &N, base_cmt: cmt<'tcx>, f_index: usize, f_ident: ast::Ident, f_ty: Ty<'tcx>) -> cmt_<'tcx> { let ret = cmt_ { hir_id: node.hir_id(), span: node.span(), mutbl: base_cmt.mutbl.inherit(), cat: Categorization::Interior(base_cmt, InteriorField(FieldIndex(f_index, f_ident.name))), ty: f_ty, note: NoteNone }; debug!("cat_field ret {:?}", ret); ret } fn cat_overloaded_place( &self, expr: &hir::Expr, base: &hir::Expr, note: Note, ) -> McResult> { debug!( "cat_overloaded_place(expr={:?}, base={:?}, note={:?})", expr, base, note, ); // 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 (region, mutbl) = match base_ty.sty { ty::Ref(region, _, mutbl) => (region, mutbl), _ => { span_bug!(expr.span, "cat_overloaded_place: base is not a reference") } }; let ref_ty = self.tcx.mk_ref(region, ty::TypeAndMut { ty: place_ty, mutbl, }); let base_cmt = Rc::new(self.cat_rvalue_node(expr.hir_id, expr.span, ref_ty)); self.cat_deref(expr, base_cmt, note) } pub fn cat_deref( &self, node: &impl HirNode, base_cmt: cmt<'tcx>, note: Note, ) -> McResult> { debug!("cat_deref: base_cmt={:?}", base_cmt); let base_cmt_ty = base_cmt.ty; let deref_ty = match base_cmt_ty.builtin_deref(true) { Some(mt) => mt.ty, None => { debug!("Explicit deref of non-derefable type: {:?}", base_cmt_ty); return Err(()); } }; let ptr = match base_cmt.ty.sty { ty::Adt(def, ..) if def.is_box() => Unique, ty::RawPtr(ref mt) => UnsafePtr(mt.mutbl), ty::Ref(r, _, mutbl) => { let bk = ty::BorrowKind::from_mutbl(mutbl); BorrowedPtr(bk, r) } ref ty => bug!("unexpected type in cat_deref: {:?}", ty) }; let ret = cmt_ { hir_id: node.hir_id(), span: node.span(), // For unique ptrs, we inherit mutability from the owning reference. mutbl: MutabilityCategory::from_pointer_kind(base_cmt.mutbl, ptr), cat: Categorization::Deref(base_cmt, ptr), ty: deref_ty, note: note, }; debug!("cat_deref ret {:?}", ret); Ok(ret) } fn cat_index(&self, elt: &N, base_cmt: cmt<'tcx>, element_ty: Ty<'tcx>, context: InteriorOffsetKind) -> McResult> { //! Creates a cmt for an indexing operation (`[]`). //! //! One subtle aspect of indexing that may not be //! immediately obvious: for anything other than a fixed-length //! vector, an operation like `x[y]` actually consists of two //! disjoint (from the point of view of borrowck) operations. //! The first is a deref of `x` to create a pointer `p` that points //! at the first element in the array. The second operation is //! an index which adds `y*sizeof(T)` to `p` to obtain the //! pointer to `x[y]`. `cat_index` will produce a resulting //! cmt containing both this deref and the indexing, //! presuming that `base_cmt` is not of fixed-length type. //! //! # Parameters //! - `elt`: the HIR node being indexed //! - `base_cmt`: the cmt of `elt` let interior_elem = InteriorElement(context); let ret = self.cat_imm_interior(elt, base_cmt, element_ty, interior_elem); debug!("cat_index ret {:?}", ret); return Ok(ret); } pub fn cat_imm_interior(&self, node: &N, base_cmt: cmt<'tcx>, interior_ty: Ty<'tcx>, interior: InteriorKind) -> cmt_<'tcx> { let ret = cmt_ { hir_id: node.hir_id(), span: node.span(), mutbl: base_cmt.mutbl.inherit(), cat: Categorization::Interior(base_cmt, interior), ty: interior_ty, note: NoteNone }; debug!("cat_imm_interior ret={:?}", ret); ret } pub fn cat_downcast_if_needed(&self, node: &N, base_cmt: cmt<'tcx>, variant_did: DefId) -> cmt<'tcx> { // univariant enums do not need downcasts let base_did = self.tcx.parent_def_id(variant_did).unwrap(); if self.tcx.adt_def(base_did).variants.len() != 1 { let base_ty = base_cmt.ty; let ret = Rc::new(cmt_ { hir_id: node.hir_id(), span: node.span(), mutbl: base_cmt.mutbl.inherit(), cat: Categorization::Downcast(base_cmt, variant_did), ty: base_ty, note: NoteNone }); debug!("cat_downcast ret={:?}", ret); ret } else { debug!("cat_downcast univariant={:?}", base_cmt); base_cmt } } pub fn cat_pattern(&self, cmt: cmt<'tcx>, pat: &hir::Pat, mut op: F) -> McResult<()> where F: FnMut(cmt<'tcx>, &hir::Pat), { self.cat_pattern_(cmt, pat, &mut op) } // FIXME(#19596) This is a workaround, but there should be a better way to do this fn cat_pattern_(&self, mut cmt: cmt<'tcx>, pat: &hir::Pat, op: &mut F) -> McResult<()> where F : FnMut(cmt<'tcx>, &hir::Pat) { // Here, `cmt` is the categorization for the value 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 cmt that represents the path // that will be taken to reach the value being matched. // // When we encounter named bindings, we take the cmt that has // been built up and pass it off to guarantee_valid() so that // we can be sure that the binding will remain valid for the // duration of the arm. // // (*2) There is subtlety concerning the correspondence between // pattern ids and types as compared to *expression* ids and // types. This is explained briefly. on the definition of the // type `cmt`, so go off and read what it says there, then // come back and I'll dive into a bit more detail here. :) OK, // back? // // In general, the id of the cmt should be the node that // "produces" the value---patterns aren't executable code // exactly, but I consider them to "execute" when they match a // value, and I consider them to produce the value that was // matched. So if you have something like: // // (FIXME: `@@3` is not legal code anymore!) // // let x = @@3; // match x { // @@y { ... } // } // // In this case, the cmt and the relevant ids would be: // // CMT Id Type of Id Type of cmt // // local(x)->@->@ // ^~~~~~~^ `x` from discr @@int @@int // ^~~~~~~~~~^ `@@y` pattern node @@int @int // ^~~~~~~~~~~~~^ `@y` pattern node @int int // // You can see that the types of the id and the cmt are in // sync in the first line, because that id is actually the id // of an expression. But once we get to pattern ids, the types // step out of sync again. So you'll see below that we always // get the type of the *subpattern* and use that. debug!("cat_pattern(pat={:?}, cmt={:?})", pat, cmt); // If (pattern) adjustments are active for this pattern, adjust the `cmt` correspondingly. // `cmt`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 `cmt` we start with a `cmt` for `foo`, and then, by traversing the // pattern, try to answer the question: given the address of `foo`, how is `x` reached? // // `&&Some(x,)` `cmt_foo` // `&Some(x,)` `deref { cmt_foo}` // `Some(x,)` `deref { deref { cmt_foo }}` // (x,)` `field0 { deref { deref { cmt_foo }}}` <- resulting cmt // // 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 { cmt_foo }}` // instead of `cmt_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.tables .pat_adjustments() .get(pat.hir_id) .map(|v| v.len()) .unwrap_or(0) { debug!("cat_pattern: applying adjustment to cmt={:?}", cmt); cmt = Rc::new(self.cat_deref(pat, cmt, NoteNone)?); } let cmt = cmt; // lose mutability debug!("cat_pattern: applied adjustment derefs to get cmt={:?}", cmt); // Invoke the callback, but only now, after the `cmt` has adjusted. // // To see that this makes sense, consider `match &Some(3) { Some(x) => { ... }}`. In that // case, the initial `cmt` 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 cmt (that for `*&Some(3)`) and the pattern // `Some(x)` (which matches). Recursing once more, `*&Some(3)` and the pattern `Some(x)` // result in the cmt `Downcast(*&Some(3)).0` associated to `x` and invoke `op` with // that (where the `ref` on `x` is implied). op(cmt.clone(), pat); match pat.node { PatKind::TupleStruct(ref qpath, ref subpats, ddpos) => { let def = self.tables.qpath_def(qpath, pat.hir_id); let (cmt, expected_len) = match def { Def::Err => { debug!("access to unresolvable pattern {:?}", pat); return Err(()) } Def::VariantCtor(def_id, CtorKind::Fn) => { let enum_def = self.tcx.parent_def_id(def_id).unwrap(); (self.cat_downcast_if_needed(pat, cmt, def_id), self.tcx.adt_def(enum_def).variant_with_id(def_id).fields.len()) } Def::StructCtor(_, CtorKind::Fn) | Def::SelfCtor(..) => { match self.pat_ty_unadjusted(&pat)?.sty { ty::Adt(adt_def, _) => { (cmt, adt_def.non_enum_variant().fields.len()) } ref ty => { span_bug!(pat.span, "tuple struct pattern unexpected type {:?}", ty); } } } def => { span_bug!(pat.span, "tuple struct pattern didn't resolve \ to variant or struct {:?}", def); } }; for (i, subpat) in subpats.iter().enumerate_and_adjust(expected_len, ddpos) { let subpat_ty = self.pat_ty_adjusted(&subpat)?; // see (*2) let interior = InteriorField(FieldIndex(i, Name::intern(&i.to_string()))); let subcmt = Rc::new(self.cat_imm_interior(pat, cmt.clone(), subpat_ty, interior)); self.cat_pattern_(subcmt, &subpat, op)?; } } PatKind::Struct(ref qpath, ref field_pats, _) => { // {f1: p1, ..., fN: pN} let def = self.tables.qpath_def(qpath, pat.hir_id); let cmt = match def { Def::Err => { debug!("access to unresolvable pattern {:?}", pat); return Err(()) }, Def::Variant(variant_did) | Def::VariantCtor(variant_did, ..) => { self.cat_downcast_if_needed(pat, cmt, variant_did) }, _ => cmt }; for fp in field_pats { let field_ty = self.pat_ty_adjusted(&fp.node.pat)?; // see (*2) let f_index = self.tcx.field_index(fp.node.id, self.tables); let cmt_field = Rc::new(self.cat_field(pat, cmt.clone(), f_index, fp.node.ident, field_ty)); self.cat_pattern_(cmt_field, &fp.node.pat, op)?; } } PatKind::Binding(.., Some(ref subpat)) => { self.cat_pattern_(cmt, &subpat, op)?; } PatKind::Tuple(ref subpats, ddpos) => { // (p1, ..., pN) let expected_len = match self.pat_ty_unadjusted(&pat)?.sty { ty::Tuple(ref tys) => tys.len(), ref ty => span_bug!(pat.span, "tuple pattern unexpected type {:?}", ty), }; for (i, subpat) in subpats.iter().enumerate_and_adjust(expected_len, ddpos) { let subpat_ty = self.pat_ty_adjusted(&subpat)?; // see (*2) let interior = InteriorField(FieldIndex(i, Name::intern(&i.to_string()))); let subcmt = Rc::new(self.cat_imm_interior(pat, cmt.clone(), subpat_ty, interior)); self.cat_pattern_(subcmt, &subpat, op)?; } } PatKind::Box(ref subpat) | PatKind::Ref(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 subcmt = Rc::new(self.cat_deref(pat, cmt, NoteNone)?); self.cat_pattern_(subcmt, &subpat, op)?; } PatKind::Slice(ref before, ref slice, ref after) => { let element_ty = match cmt.ty.builtin_index() { Some(ty) => ty, None => { debug!("Explicit index of non-indexable type {:?}", cmt); return Err(()); } }; let context = InteriorOffsetKind::Pattern; let elt_cmt = Rc::new(self.cat_index(pat, cmt, element_ty, context)?); for before_pat in before { self.cat_pattern_(elt_cmt.clone(), &before_pat, op)?; } if let Some(ref slice_pat) = *slice { self.cat_pattern_(elt_cmt.clone(), &slice_pat, op)?; } for after_pat in after { self.cat_pattern_(elt_cmt.clone(), &after_pat, op)?; } } PatKind::Path(_) | PatKind::Binding(.., None) | PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild => { // always ok } } Ok(()) } } #[derive(Clone, Debug)] pub enum Aliasability { FreelyAliasable(AliasableReason), NonAliasable, ImmutableUnique(Box), } #[derive(Copy, Clone, Debug)] pub enum AliasableReason { AliasableBorrowed, AliasableStatic, AliasableStaticMut, } impl<'tcx> cmt_<'tcx> { pub fn guarantor(&self) -> cmt_<'tcx> { //! Returns `self` after stripping away any derefs or //! interior content. The return value is basically the `cmt` which //! determines how long the value in `self` remains live. match self.cat { Categorization::Rvalue(..) | Categorization::StaticItem | Categorization::Local(..) | Categorization::Deref(_, UnsafePtr(..)) | Categorization::Deref(_, BorrowedPtr(..)) | Categorization::Upvar(..) => { (*self).clone() } Categorization::Downcast(ref b, _) | Categorization::Interior(ref b, _) | Categorization::Deref(ref b, Unique) => { b.guarantor() } } } /// Returns `FreelyAliasable(_)` if this place represents a freely aliasable pointer type. pub fn freely_aliasable(&self) -> Aliasability { // Maybe non-obvious: copied upvars can only be considered // non-aliasable in once closures, since any other kind can be // aliased and eventually recused. match self.cat { Categorization::Deref(ref b, BorrowedPtr(ty::MutBorrow, _)) | Categorization::Deref(ref b, BorrowedPtr(ty::UniqueImmBorrow, _)) | Categorization::Deref(ref b, Unique) | Categorization::Downcast(ref b, _) | Categorization::Interior(ref b, _) => { // Aliasability depends on base cmt b.freely_aliasable() } Categorization::Rvalue(..) | Categorization::Local(..) | Categorization::Upvar(..) | Categorization::Deref(_, UnsafePtr(..)) => { // yes, it's aliasable, but... NonAliasable } Categorization::StaticItem => { if self.mutbl.is_mutable() { FreelyAliasable(AliasableStaticMut) } else { FreelyAliasable(AliasableStatic) } } Categorization::Deref(_, BorrowedPtr(ty::ImmBorrow, _)) => { FreelyAliasable(AliasableBorrowed) } } } // Digs down through one or two layers of deref and grabs the // Categorization of the cmt for the upvar if a note indicates there is // one. pub fn upvar_cat(&self) -> Option<&Categorization<'tcx>> { match self.note { NoteClosureEnv(..) | NoteUpvarRef(..) => { Some(match self.cat { Categorization::Deref(ref inner, _) => { match inner.cat { Categorization::Deref(ref inner, _) => &inner.cat, Categorization::Upvar(..) => &inner.cat, _ => bug!() } } _ => bug!() }) } NoteIndex | NoteNone => None } } pub fn descriptive_string(&self, tcx: TyCtxt<'_, '_, '_>) -> Cow<'static, str> { match self.cat { Categorization::StaticItem => { "static item".into() } Categorization::Rvalue(..) => { "non-place".into() } Categorization::Local(vid) => { if tcx.hir.is_argument(vid) { "argument" } else { "local variable" }.into() } Categorization::Deref(_, pk) => { match self.upvar_cat() { Some(&Categorization::Upvar(ref var)) => { var.to_string().into() } Some(_) => bug!(), None => { match pk { Unique => { "`Box` content" } UnsafePtr(..) => { "dereference of raw pointer" } BorrowedPtr(..) => { match self.note { NoteIndex => "indexed content", _ => "borrowed content" } } }.into() } } } Categorization::Interior(_, InteriorField(..)) => { "field".into() } Categorization::Interior(_, InteriorElement(InteriorOffsetKind::Index)) => { "indexed content".into() } Categorization::Interior(_, InteriorElement(InteriorOffsetKind::Pattern)) => { "pattern-bound indexed content".into() } Categorization::Upvar(ref var) => { var.to_string().into() } Categorization::Downcast(ref cmt, _) => { cmt.descriptive_string(tcx).into() } } } } pub fn ptr_sigil(ptr: PointerKind<'_>) -> &'static str { match ptr { Unique => "Box", BorrowedPtr(ty::ImmBorrow, _) => "&", BorrowedPtr(ty::MutBorrow, _) => "&mut", BorrowedPtr(ty::UniqueImmBorrow, _) => "&unique", UnsafePtr(_) => "*", } } impl fmt::Debug for InteriorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { InteriorField(FieldIndex(_, info)) => write!(f, "{}", info), InteriorElement(..) => write!(f, "[]"), } } } impl fmt::Debug for Upvar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}/{:?}", self.id, self.kind) } } impl fmt::Display for Upvar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let kind = match self.kind { ty::ClosureKind::Fn => "Fn", ty::ClosureKind::FnMut => "FnMut", ty::ClosureKind::FnOnce => "FnOnce", }; write!(f, "captured outer variable in an `{}` closure", kind) } }