rustc_utils/mir/
place.rs

1//! Utilities for [`Place`].
2
3use std::{borrow::Cow, collections::VecDeque};
4
5use log::{trace, warn};
6use rustc_abi::{FieldIdx, VariantIdx};
7use rustc_data_structures::fx::{FxHashMap as HashMap, FxHashSet as HashSet};
8use rustc_hir::def_id::DefId;
9use rustc_infer::infer::TyCtxtInferExt;
10use rustc_middle::{
11  mir::{
12    Body, HasLocalDecls, Local, Location, Mutability, Place, PlaceElem, PlaceRef,
13    ProjectionElem, RETURN_PLACE, VarDebugInfo, VarDebugInfoContents,
14    visit::{PlaceContext, Visitor},
15  },
16  traits::ObligationCause,
17  ty::{self, AdtKind, Region, RegionKind, RegionVid, Ty, TyCtxt, TyKind, TypeVisitor},
18};
19use rustc_trait_selection::traits::NormalizeExt;
20use rustc_type_ir::TypingMode;
21
22use crate::{AdtDefExt, SpanExt};
23
24/// A MIR [`Visitor`] which collects all [`Place`]s that appear in the visited object.
25#[derive(Default)]
26pub struct PlaceCollector<'tcx>(pub Vec<Place<'tcx>>);
27
28impl<'tcx> Visitor<'tcx> for PlaceCollector<'tcx> {
29  fn visit_place(
30    &mut self,
31    place: &Place<'tcx>,
32    _context: PlaceContext,
33    _location: Location,
34  ) {
35    self.0.push(*place);
36  }
37}
38
39/// Extension trait for [`Place`].
40pub trait PlaceExt<'tcx> {
41  /// Creates a new [`Place`].
42  fn make(local: Local, projection: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self;
43
44  /// Converts a [`PlaceRef`] into an owned [`Place`].
45  fn from_ref(place: PlaceRef<'tcx>, tcx: TyCtxt<'tcx>) -> Self;
46
47  /// Creates a new [`Place`] with an empty projection.
48  fn from_local(local: Local, tcx: TyCtxt<'tcx>) -> Self;
49
50  /// Returns true if `self` is a projection of an argument local.
51  fn is_arg(&self, body: &Body<'tcx>) -> bool;
52
53  /// Returns true if `self` cannot be resolved further to another place.
54  ///
55  /// This is true if one of the following is true:
56  /// - `self` contains no dereference (`*`) projections
57  /// - `self` is the dereference of (a projection of) an argument to `body`
58  /// - all dereferences in `self` are dereferences of a `Box`
59  fn is_direct(&self, body: &Body<'tcx>, tcx: TyCtxt<'tcx>) -> bool;
60
61  /// Returns an iterator over all prefixes of `self`'s projection that are references,
62  ///  along with the suffix of the remaining projection.
63  fn refs_in_projection(
64    self,
65    body: &Body<'tcx>,
66    tcx: TyCtxt<'tcx>,
67  ) -> impl Iterator<Item = (PlaceRef<'tcx>, &'tcx [PlaceElem<'tcx>])>;
68
69  /// Returns all possible projections of `self` that are references.
70  ///
71  /// The output data structure groups the resultant places based on the region of the references.
72  fn interior_pointers(
73    &self,
74    tcx: TyCtxt<'tcx>,
75    body: &Body<'tcx>,
76    def_id: DefId,
77  ) -> HashMap<RegionVid, Vec<(Place<'tcx>, Mutability)>>;
78
79  /// Returns all possible projections of `self` that do not go through a reference,
80  /// i.e. the set of fields directly in the structure referred by `self`.
81  fn interior_places(
82    &self,
83    tcx: TyCtxt<'tcx>,
84    body: &Body<'tcx>,
85    def_id: DefId,
86  ) -> HashSet<Place<'tcx>>;
87
88  /// Returns all possible projections of `self`.
89  fn interior_paths(
90    &self,
91    tcx: TyCtxt<'tcx>,
92    body: &Body<'tcx>,
93    def_id: DefId,
94  ) -> HashSet<Place<'tcx>>;
95
96  /// Returns a pretty representation of a place that uses debug info when available.
97  fn to_string(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> Option<String>;
98
99  /// Erases/normalizes information in a place to ensure stable comparisons between places.
100  ///
101  /// Consider a place `_1: &'1 <T as SomeTrait>::Foo[2]`.
102  ///   We might encounter this type with a different region, e.g. `&'2`.
103  ///   We might encounter this type with a more specific type for the associated type, e.g. `&'1 [i32][0]`.
104  /// To account for this variation, we normalize associated types,
105  ///   erase regions, and normalize projections.
106  fn normalize(&self, tcx: TyCtxt<'tcx>, def_id: DefId) -> Place<'tcx>;
107
108  /// Returns true if this place's base [`Local`] corresponds to code that is visible in the source.
109  fn is_source_visible(&self, tcx: TyCtxt, body: &Body) -> bool;
110}
111
112impl<'tcx> PlaceExt<'tcx> for Place<'tcx> {
113  fn make(local: Local, projection: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self {
114    Place {
115      local,
116      projection: tcx.mk_place_elems(projection),
117    }
118  }
119
120  fn from_ref(place: PlaceRef<'tcx>, tcx: TyCtxt<'tcx>) -> Self {
121    Self::make(place.local, place.projection, tcx)
122  }
123
124  fn from_local(local: Local, tcx: TyCtxt<'tcx>) -> Self {
125    Place::make(local, &[], tcx)
126  }
127
128  fn is_arg(&self, body: &Body<'tcx>) -> bool {
129    let i = self.local.as_usize();
130    i > 0 && i - 1 < body.arg_count
131  }
132
133  fn is_direct(&self, body: &Body<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
134    !self.is_indirect()
135      || self.is_arg(body)
136      || self.refs_in_projection(body, tcx).next().is_none()
137  }
138
139  fn refs_in_projection(
140    self,
141    body: &Body<'tcx>,
142    tcx: TyCtxt<'tcx>,
143  ) -> impl Iterator<Item = (PlaceRef<'tcx>, &'tcx [PlaceElem<'tcx>])> {
144    self
145      .iter_projections()
146      .enumerate()
147      .filter_map(move |(i, (place_ref, elem))| match elem {
148        ProjectionElem::Deref => {
149          let ptr = PlaceRef {
150            local: self.local,
151            projection: &self.projection[.. i],
152          };
153          let after = &self.projection[i + 1 ..];
154          (!place_ref.ty(body.local_decls(), tcx).ty.is_box()).then_some((ptr, after))
155        }
156        _ => None,
157      })
158  }
159
160  fn interior_pointers(
161    &self,
162    tcx: TyCtxt<'tcx>,
163    body: &Body<'tcx>,
164    def_id: DefId,
165  ) -> HashMap<RegionVid, Vec<(Place<'tcx>, Mutability)>> {
166    let ty = self.ty(body.local_decls(), tcx).ty;
167    let mut region_collector = RegionVisitor::<RegionMemberCollector>::new(
168      tcx,
169      def_id,
170      *self,
171      if
172      /*shallow*/
173      false {
174        StoppingCondition::AfterRefs
175      } else {
176        StoppingCondition::None
177      },
178    );
179    region_collector.visit_ty(ty);
180    region_collector.into_inner().0
181  }
182
183  fn interior_places(
184    &self,
185    tcx: TyCtxt<'tcx>,
186    body: &Body<'tcx>,
187    def_id: DefId,
188  ) -> HashSet<Place<'tcx>> {
189    let ty = self.ty(body.local_decls(), tcx).ty;
190    let mut region_collector = RegionVisitor::<VisitedPlacesCollector>::new(
191      tcx,
192      def_id,
193      *self,
194      StoppingCondition::BeforeRefs,
195    );
196    region_collector.visit_ty(ty);
197    region_collector.into_inner().0
198  }
199
200  fn interior_paths(
201    &self,
202    tcx: TyCtxt<'tcx>,
203    body: &Body<'tcx>,
204    def_id: DefId,
205  ) -> HashSet<Place<'tcx>> {
206    let ty = self.ty(body.local_decls(), tcx).ty;
207    let mut region_collector = RegionVisitor::<VisitedPlacesCollector>::new(
208      tcx,
209      def_id,
210      *self,
211      StoppingCondition::None,
212    );
213    region_collector.visit_ty(ty);
214    region_collector.into_inner().0
215  }
216
217  fn to_string(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> Option<String> {
218    // Get the local's debug name from the Body's VarDebugInfo
219    let local_name = if self.local == RETURN_PLACE {
220      Cow::Borrowed("RETURN")
221    } else {
222      let get_local_name = |info: &VarDebugInfo<'tcx>| match info.value {
223        VarDebugInfoContents::Place(place) if place.local == self.local => info
224          .source_info
225          .span
226          .as_local(body.span)
227          .map(|_| info.name.to_string()),
228        _ => None,
229      };
230      let local_name = body.var_debug_info.iter().find_map(get_local_name)?;
231      Cow::Owned(local_name)
232    };
233
234    #[derive(Copy, Clone)]
235    enum ElemPosition {
236      Prefix,
237      Suffix,
238    }
239
240    // Turn each PlaceElem into a prefix (e.g. * for deref) or a suffix
241    // (e.g. .field for projection).
242    let elem_to_string = |(index, (place, elem)): (
243      usize,
244      (PlaceRef<'tcx>, PlaceElem<'tcx>),
245    )|
246     -> (ElemPosition, Cow<'static, str>) {
247      match elem {
248        ProjectionElem::Deref => (ElemPosition::Prefix, "*".into()),
249
250        ProjectionElem::Field(field, _) => {
251          let ty = place.ty(&body.local_decls, tcx).ty;
252
253          let field_name = match ty.kind() {
254            TyKind::Adt(def, _substs) => {
255              let fields = match def.adt_kind() {
256                AdtKind::Struct => &def.non_enum_variant().fields,
257                AdtKind::Enum => {
258                  let Some(PlaceElem::Downcast(_, variant_idx)) =
259                    self.projection.get(index - 1)
260                  else {
261                    unimplemented!()
262                  };
263                  &def.variant(*variant_idx).fields
264                }
265                kind => unimplemented!("{kind:?}"),
266              };
267
268              fields[field].ident(tcx).to_string()
269            }
270
271            TyKind::Tuple(_) => field.as_usize().to_string(),
272
273            TyKind::Closure(def_id, _substs) => match def_id.as_local() {
274              Some(local_def_id) => {
275                let captures = tcx.closure_captures(local_def_id);
276                captures[field.as_usize()].var_ident.to_string()
277              }
278              None => field.as_usize().to_string(),
279            },
280
281            kind => unimplemented!("{kind:?}"),
282          };
283
284          (ElemPosition::Suffix, format!(".{field_name}").into())
285        }
286        ProjectionElem::Downcast(sym, _) => {
287          let variant = sym.map(|s| s.to_string()).unwrap_or_else(|| "??".into());
288          (ElemPosition::Suffix, format!("@{variant}",).into())
289        }
290
291        ProjectionElem::Index(_) => (ElemPosition::Suffix, "[_]".into()),
292        kind => unimplemented!("{kind:?}"),
293      }
294    };
295
296    let (positions, contents): (Vec<_>, Vec<_>) = self
297      .iter_projections()
298      .enumerate()
299      .map(elem_to_string)
300      .unzip();
301
302    // Combine the prefixes and suffixes into a corresponding sequence
303    let mut parts = VecDeque::from([local_name]);
304    for (i, string) in contents.into_iter().enumerate() {
305      match positions[i] {
306        ElemPosition::Prefix => {
307          parts.push_front(string);
308          if matches!(positions.get(i + 1), Some(ElemPosition::Suffix)) {
309            parts.push_front(Cow::Borrowed("("));
310            parts.push_back(Cow::Borrowed(")"));
311          }
312        }
313        ElemPosition::Suffix => parts.push_back(string),
314      }
315    }
316
317    let full = parts.make_contiguous().join("");
318    Some(full)
319  }
320
321  fn normalize(&self, tcx: TyCtxt<'tcx>, def_id: DefId) -> Place<'tcx> {
322    let param_env = tcx.param_env(def_id);
323    let place = tcx.erase_regions(*self);
324    let infcx = tcx.infer_ctxt().build(TypingMode::post_borrowck_analysis(
325      tcx,
326      def_id.expect_local(),
327    ));
328    let place = infcx
329      .at(&ObligationCause::dummy(), param_env)
330      .normalize(place)
331      .value;
332
333    let projection = place
334      .projection
335      .into_iter()
336      .filter_map(|elem| match elem {
337        // Map all indexes [i] to [0] since they should be considered equal
338        ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => {
339          Some(ProjectionElem::Index(Local::from_usize(0)))
340        }
341        // Ignore subslices, they should be treated the same as the
342        // full slice
343        ProjectionElem::Subslice { .. } => None,
344        _ => Some(elem),
345      })
346      .collect::<Vec<_>>();
347
348    Place::make(place.local, &projection, tcx)
349  }
350
351  fn is_source_visible(&self, _tcx: TyCtxt, body: &Body) -> bool {
352    let local = self.local;
353    let local_info = &body.local_decls[local];
354    let is_loc = local_info.is_user_variable();
355    let from_desugaring = local_info.from_compiler_desugaring();
356    let from_expansion = local_info.source_info.span.from_expansion();
357
358    // The assumption is that for a place to be source visible it needs to:
359    // 1. Be a local declaration.
360    // 2. Not be from a compiler desugaring.
361    // 3. Not be from a macro expansion (basically also a desugaring).
362    is_loc && !from_desugaring && !from_expansion
363  }
364}
365
366#[derive(Copy, Clone)]
367enum StoppingCondition {
368  None,
369  BeforeRefs,
370  AfterRefs,
371}
372
373trait RegionVisitorDispatcher<'tcx> {
374  fn on_visit_place(&mut self, _: Place<'tcx>) {}
375  fn on_visit_type(&mut self, _: Ty<'tcx>) {}
376  fn on_visit_region_member(&mut self, _: RegionVid, _: Place<'tcx>, _: Mutability) {}
377}
378
379#[derive(Default)]
380struct VisitedPlacesCollector<'tcx>(HashSet<Place<'tcx>>);
381
382impl<'tcx> RegionVisitorDispatcher<'tcx> for VisitedPlacesCollector<'tcx> {
383  fn on_visit_place(&mut self, place: Place<'tcx>) {
384    self.0.insert(place);
385  }
386}
387
388#[derive(Default)]
389struct RegionMemberCollector<'tcx>(HashMap<RegionVid, Vec<(Place<'tcx>, Mutability)>>);
390
391impl<'tcx> RegionVisitorDispatcher<'tcx> for RegionMemberCollector<'tcx> {
392  fn on_visit_region_member(
393    &mut self,
394    key: RegionVid,
395    place: Place<'tcx>,
396    mutbl: Mutability,
397  ) {
398    self.0.entry(key).or_default().push((place, mutbl));
399  }
400}
401
402struct RegionVisitor<'tcx, Dispatcher> {
403  tcx: TyCtxt<'tcx>,
404  def_id: DefId,
405  /// Base local of the place we are collecting regions for.
406  local: Local,
407  /// List of projections to apply to the base local in order to reach the
408  /// child place currently under consideration.
409  ///
410  /// Starts out as the projections in the input place.
411  place_stack: Vec<PlaceElem<'tcx>>,
412  /// Sequence of parent types to reach the place currently under consideration.
413  /// Correspond to the projections in `place_stack`.
414  ty_stack: Vec<Ty<'tcx>>,
415  /// Callbacks
416  dispatcher: Dispatcher,
417  stop_at: StoppingCondition,
418}
419
420impl<'tcx, Dispatcher: Default> RegionVisitor<'tcx, Dispatcher> {
421  /// Construct a new [`CollectRegions`] visitor.
422  ///
423  /// By itself the visitor only implements the traversal. Actual accumulation
424  /// of useful information is done by the `Dispatcher`.
425  fn new(
426    tcx: TyCtxt<'tcx>,
427    def_id: DefId,
428    place: Place<'tcx>,
429    stop_at: StoppingCondition,
430  ) -> Self {
431    Self {
432      tcx,
433      def_id,
434      local: place.local,
435      place_stack: place.projection.to_vec(),
436      ty_stack: Vec::new(),
437      dispatcher: Default::default(),
438      stop_at,
439    }
440  }
441
442  fn into_inner(self) -> Dispatcher {
443    self.dispatcher
444  }
445}
446
447/// Used to describe aliases of owned and raw pointers.
448pub const UNKNOWN_REGION: RegionVid = RegionVid::MAX;
449
450impl<'tcx, Dispatcher: RegionVisitorDispatcher<'tcx>> TypeVisitor<TyCtxt<'tcx>>
451  for RegionVisitor<'tcx, Dispatcher>
452{
453  fn visit_ty(&mut self, ty: Ty<'tcx>) {
454    let tcx = self.tcx;
455    if self.ty_stack.contains(&ty) {
456      return;
457    }
458
459    trace!(
460      "exploring {:?} with {ty:?}",
461      Place::make(self.local, &self.place_stack, tcx)
462    );
463
464    self.ty_stack.push(ty);
465
466    match ty.kind() {
467      _ if ty.is_box() => {
468        self.place_stack.push(ProjectionElem::Deref);
469        self.visit_ty(ty.boxed_ty().expect("Cannot unbox boxed type??"));
470        self.place_stack.pop();
471      }
472
473      TyKind::Tuple(fields) => {
474        for (i, field) in fields.iter().enumerate() {
475          self
476            .place_stack
477            .push(ProjectionElem::Field(FieldIdx::from_usize(i), field));
478          self.visit_ty(field);
479          self.place_stack.pop();
480        }
481      }
482
483      TyKind::Adt(adt_def, subst) => match adt_def.adt_kind() {
484        ty::AdtKind::Struct => {
485          for (i, field) in adt_def.all_visible_fields(self.def_id, tcx).enumerate() {
486            let ty = field.ty(tcx, subst);
487            self
488              .place_stack
489              .push(ProjectionElem::Field(FieldIdx::from_usize(i), ty));
490            self.visit_ty(ty);
491            self.place_stack.pop();
492          }
493        }
494        ty::AdtKind::Union => {
495          // unsafe, so ignore
496        }
497        ty::AdtKind::Enum => {
498          for (i, variant) in adt_def.variants().iter().enumerate() {
499            let variant_index = VariantIdx::from_usize(i);
500            let cast = PlaceElem::Downcast(
501              Some(adt_def.variant(variant_index).ident(tcx).name),
502              variant_index,
503            );
504            self.place_stack.push(cast);
505            for (j, field) in variant.fields.iter().enumerate() {
506              let ty = field.ty(tcx, subst);
507              let field = ProjectionElem::Field(FieldIdx::from_usize(j), ty);
508              self.place_stack.push(field);
509              self.visit_ty(ty);
510              self.place_stack.pop();
511            }
512            self.place_stack.pop();
513          }
514        }
515      },
516
517      TyKind::Array(elem_ty, _) | TyKind::Slice(elem_ty) => {
518        self
519          .place_stack
520          .push(ProjectionElem::Index(Local::from_usize(0)));
521        self.visit_ty(*elem_ty);
522        self.place_stack.pop();
523      }
524
525      TyKind::Ref(region, elem_ty, _) => match self.stop_at {
526        StoppingCondition::None => {
527          self.visit_region(*region);
528          self.place_stack.push(ProjectionElem::Deref);
529          self.visit_ty(*elem_ty);
530          self.place_stack.pop();
531        }
532        StoppingCondition::AfterRefs => {
533          self.visit_region(*region);
534        }
535        StoppingCondition::BeforeRefs => {}
536      },
537
538      TyKind::Closure(_, substs) | TyKind::Coroutine(_, substs) => {
539        self.visit_ty(substs.as_closure().tupled_upvars_ty());
540      }
541
542      TyKind::RawPtr(ty, _) => {
543        self.visit_region(Region::new_var(tcx, UNKNOWN_REGION));
544        self.place_stack.push(ProjectionElem::Deref);
545        self.visit_ty(*ty);
546        self.place_stack.pop();
547      }
548
549      TyKind::FnDef(..)
550      | TyKind::FnPtr(..)
551      | TyKind::Foreign(..)
552      | TyKind::Dynamic(..)
553      | TyKind::Param(..)
554      | TyKind::Never => {}
555
556      _ if ty.is_primitive_ty() => {}
557
558      _ => warn!("unimplemented {ty:?} ({:?})", ty.kind()),
559    }
560
561    // let inherent_impls = tcx.inherent_impls(self.def_id);
562    // let traits = tcx.infer_ctxt().enter(|infcx| {
563    //   let param_env = tcx.param_env(self.def_id);
564    //   self
565    //     .tcx
566    //     .all_traits()
567    //     .filter(|trait_def_id| {
568    //       let result = infcx.type_implements_trait(*trait_def_id, ty, params, param_env);
569    //       matches!(
570    //         result,
571    //         EvaluationResult::EvaluatedToOk
572    //           | EvaluationResult::EvaluatedToOkModuloRegions
573    //       )
574    //     })
575    //     .collect::<Vec<_>>()
576    // });
577
578    // let fns = inherent_impls.iter().chain(&traits).flat_map(|def_id| {
579    //   let items = tcx.associated_items(def_id).in_definition_order();
580    //   items.filter_map(|item| match item.kind {
581    //     AssocKind::Fn => Some(tcx.fn_sig(item.def_id)),
582    //     _ => None,
583    //   })
584    // });
585
586    // // for f in fns {
587    // //   f.
588    // // }
589
590    self
591      .dispatcher
592      .on_visit_place(Place::make(self.local, &self.place_stack, tcx));
593
594    self.dispatcher.on_visit_type(ty);
595
596    self.ty_stack.pop();
597  }
598
599  fn visit_region(&mut self, region: ty::Region<'tcx>) -> Self::Result {
600    trace!("visiting region {region:?}");
601    let region = match region.kind() {
602      RegionKind::ReVar(region) => region,
603      RegionKind::ReStatic => RegionVid::from_usize(0),
604      RegionKind::ReErased | RegionKind::ReLateParam(_) => {
605        return;
606      }
607      _ => unreachable!("{:?}: {:?}", self.ty_stack.first().unwrap(), region),
608    };
609
610    let mutability = if self
611      .ty_stack
612      .iter()
613      .any(|ty| ty.is_ref() && ty.ref_mutability().unwrap() == Mutability::Not)
614    {
615      Mutability::Not
616    } else {
617      Mutability::Mut
618    };
619
620    let place = Place::make(self.local, &self.place_stack, self.tcx);
621
622    self
623      .dispatcher
624      .on_visit_region_member(region, place, mutability);
625
626    // for initialization setup of Aliases::build
627    //
628    // uses `once_with` so that the place is only created when the iterator is
629    // not ignored (e.g. `Places != Ignore`)
630    self
631      .dispatcher
632      .on_visit_place(self.tcx.mk_place_deref(place));
633  }
634}
635
636#[cfg(test)]
637mod test {
638  use rustc_borrowck::consumers::BodyWithBorrowckFacts;
639  use rustc_hir::BodyId;
640  use rustc_middle::{
641    mir::{Place, PlaceElem},
642    ty::TyCtxt,
643  };
644
645  use crate::{
646    BodyExt, PlaceExt,
647    test_utils::{self, Placer, compare_sets},
648  };
649
650  #[test]
651  fn test_place_arg_direct() {
652    let input = r"
653fn foobar(x: &i32) {
654  let y = 1;
655  let z = &y;
656  let k = Box::new(*x);
657  let ref_k = &k;
658  let box_ref = Box::new(x);
659}
660";
661    test_utils::compile_body(input, |tcx, _, body_with_facts| {
662      let body = &body_with_facts.body;
663      let name_map = body.debug_info_name_map();
664      let x = Place::from_local(name_map["x"], tcx);
665      assert!(x.is_arg(body));
666      assert!(x.is_direct(body, tcx));
667      assert!(Place::make(x.local, &[PlaceElem::Deref], tcx).is_direct(body, tcx));
668
669      let y = Place::from_local(name_map["y"], tcx);
670      assert!(!y.is_arg(body));
671      assert!(y.is_direct(body, tcx));
672
673      let z = Place::from_local(name_map["z"], tcx);
674      assert!(!z.is_arg(body));
675      assert!(z.is_direct(body, tcx));
676      assert!(!Place::make(z.local, &[PlaceElem::Deref], tcx).is_direct(body, tcx));
677
678      let k = Place::from_local(name_map["k"], tcx);
679      assert!(!k.is_arg(body));
680      assert!(k.is_direct(body, tcx));
681      assert!(Place::make(k.local, &[PlaceElem::Deref], tcx).is_direct(body, tcx));
682      let deref_k = Place::make(k.local, &[PlaceElem::Deref], tcx);
683      assert!(deref_k.is_direct(body, tcx));
684      assert!(!deref_k.is_arg(body));
685      assert_eq!(deref_k.refs_in_projection(body, tcx).count(), 0);
686
687      let ref_k = Place::from_local(name_map["ref_k"], tcx);
688      assert!(!ref_k.is_arg(body));
689      assert!(k.is_direct(body, tcx));
690      let deref_ref_k =
691        Place::make(ref_k.local, &[PlaceElem::Deref, PlaceElem::Deref], tcx);
692      assert!(!deref_ref_k.is_direct(body, tcx));
693      assert_eq!(deref_ref_k.refs_in_projection(body, tcx).count(), 1);
694
695      let box_ref = Place::from_local(name_map["box_ref"], tcx);
696      assert!(!box_ref.is_arg(body));
697      assert!(!box_ref.is_indirect());
698      let box_ref_deref = Place::make(box_ref.local, &[PlaceElem::Deref], tcx);
699      assert_eq!(box_ref_deref.refs_in_projection(body, tcx).count(), 0);
700      assert!(box_ref_deref.is_direct(body, tcx));
701      let box_ref_deref_deref = box_ref_deref.project_deeper(&[PlaceElem::Deref], tcx);
702      assert_eq!(box_ref_deref_deref.refs_in_projection(body, tcx).count(), 1);
703      assert!(!box_ref_deref_deref.is_direct(body, tcx));
704    });
705  }
706
707  #[test]
708  fn test_place_to_string() {
709    let input = r"
710struct Point { x: usize, y: usize }
711fn main() {
712  let x = (0, 0);
713  let y = Some(1);
714  let z = &[Some((0, 1))];
715  let w = (&y,);
716  let p = &Point { x: 0, y: 0 };
717}";
718    test_utils::compile_body(input, |tcx, _, body_with_facts| {
719      let body = &body_with_facts.body;
720      let p = Placer::new(tcx, body);
721
722      let x = p.local("x").mk();
723      let x_1 = p.local("x").field(1).mk();
724      let y_some_0 = p.local("y").downcast(1).field(0).mk();
725      let z_deref_some_0_1 = p
726        .local("z")
727        .deref()
728        .index(0)
729        .downcast(1)
730        .field(0)
731        .field(1)
732        .mk();
733      let w_0_deref = p.local("w").field(0).deref().mk();
734      let w_0_deref_some = p.local("w").field(0).deref().downcast(1).mk();
735      let p_deref_x = p.local("p").deref().field(0).mk();
736
737      let tests = [
738        (x, "x"),
739        (x_1, "x.1"),
740        (y_some_0, "y@Some.0"),
741        (z_deref_some_0_1, "(*z)[_]@Some.0.1"),
742        (w_0_deref, "*w.0"),
743        (w_0_deref_some, "(*w.0)@Some"),
744        (p_deref_x, "(*p).x"),
745      ];
746
747      for (place, expected) in tests {
748        assert_eq!(place.to_string(tcx, body).unwrap(), expected);
749      }
750    });
751  }
752
753  #[test]
754  fn test_place_visitors() {
755    let input = r"
756fn main() {
757  let x = 0;
758  let y = (0, &x);
759}
760";
761    fn callback<'tcx>(
762      tcx: TyCtxt<'tcx>,
763      body_id: BodyId,
764      body_with_facts: &BodyWithBorrowckFacts<'tcx>,
765    ) {
766      let body = &body_with_facts.body;
767      let def_id = tcx.hir_body_owner_def_id(body_id).to_def_id();
768      let p = Placer::new(tcx, body);
769
770      let y = p.local("y").mk();
771      let y0 = p.local("y").field(0).mk();
772      let y1 = p.local("y").field(1).mk();
773      let y1_deref = p.local("y").field(1).deref().mk();
774
775      compare_sets(y.interior_paths(tcx, body, def_id), [y, y0, y1, y1_deref]);
776
777      compare_sets(y.interior_places(tcx, body, def_id), [y, y0, y1]);
778
779      compare_sets(
780        y.interior_pointers(tcx, body, def_id)
781          .into_values()
782          .flat_map(|vs| vs.into_iter().map(|(p, _)| p)),
783        [y1],
784      );
785    }
786    test_utils::compile_body(input, callback);
787  }
788}