rustc_utils/source_map/spanner/
mir_span.rs

1use either::Either;
2use log::trace;
3use rustc_middle::mir::{
4  self, Body, FakeReadCause, HasLocalDecls, Place, RETURN_PLACE, Statement,
5  StatementKind, Terminator, TerminatorKind,
6  visit::{
7    MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext,
8    Visitor as MirVisitor,
9  },
10};
11use rustc_span::SpanData;
12use smallvec::{SmallVec, smallvec};
13
14use super::Spanner;
15use crate::{BodyExt, PlaceExt, SpanExt, mir::location_or_arg::LocationOrArg};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct MirSpannedPlace<'tcx> {
19  pub place: mir::Place<'tcx>,
20  pub span: SpanData,
21  pub locations: SmallVec<[LocationOrArg; 1]>,
22}
23
24pub struct MirSpanCollector<'a, 'tcx>(pub &'a mut Spanner<'tcx>, pub &'a Body<'tcx>);
25
26macro_rules! try_span {
27  ($self:expr, $span:expr) => {
28    match $span.as_local($self.0.item_span) {
29      Some(span) if !$self.0.invalid_span(span) => span,
30      _ => {
31        return;
32      }
33    }
34  };
35}
36
37impl<'tcx> MirVisitor<'tcx> for MirSpanCollector<'_, 'tcx> {
38  fn visit_body(&mut self, body: &Body<'tcx>) {
39    self.super_body(body);
40
41    // Add the return type as a spanned place representing all return locations
42    let span = body.local_decls()[RETURN_PLACE].source_info.span;
43    let span = try_span!(self, span);
44    let locations = body
45      .all_returns()
46      .map(LocationOrArg::Location)
47      .collect::<SmallVec<_>>();
48    self.0.mir_spans.push(MirSpannedPlace {
49      span: span.data(),
50      locations,
51      place: Place::from_local(RETURN_PLACE, self.0.tcx),
52    });
53  }
54
55  fn visit_place(
56    &mut self,
57    place: &mir::Place<'tcx>,
58    context: PlaceContext,
59    location: mir::Location,
60  ) {
61    trace!("place={place:?} context={context:?} location={location:?}");
62
63    // MIR will sometimes include places assigned to unit, e.g.
64    //   if true { let x = 1; } else { let x = 2; }
65    // then the entire block will have a place with unit value.
66    // To avoid letting that block be selectable, we ignore values with unit type.
67    // This is a hack, but not sure if there's a better way?
68    let body = &self.1;
69    if place.ty(body.local_decls(), self.0.tcx).ty.is_unit() {
70      return;
71    }
72
73    // Three cases, shown by example:
74    //   fn foo(x: i32) {
75    //     let y = x + 1;
76    //   }
77    // If the user selects...
78    // * "x: i32" -- this span is contained in the LocalDecls for _1,
79    //   which is represented by NonUseContext::VarDebugInfo
80    // * "x + 1" -- MIR will generate a temporary to assign x into, whose
81    //   span is given to "x". That corresponds to MutatingUseContext::Store
82    // * "y" -- this corresponds to NonMutatingUseContext::Inspect
83    let (span, locations) = match context {
84      PlaceContext::MutatingUse(MutatingUseContext::Store)
85      | PlaceContext::NonMutatingUse(
86        NonMutatingUseContext::Copy | NonMutatingUseContext::Move,
87      ) => {
88        let source_info = body.source_info(location);
89        (source_info.span, smallvec![LocationOrArg::Location(
90          location
91        )])
92      }
93      PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) => {
94        let source_info = body.source_info(location);
95        // For a statement like `let y = x + 1`, if the user selects `y`,
96        // then the only location that contains the source-map for `y` is a `FakeRead`.
97        // However, for slicing we want to give the location that actually sets `y`.
98        // So we search through the body to find the locations that assign to `y`.
99        let locations = match body.stmt_at(location) {
100          Either::Left(Statement {
101            kind:
102              StatementKind::FakeRead(box (
103                FakeReadCause::ForLet(_) | FakeReadCause::ForMatchedPlace(_),
104                _,
105              )),
106            ..
107          }) => match LocationOrArg::from_place(*place, body) {
108            Some(arg_location) => smallvec![arg_location],
109            None => {
110              let locations = assigning_locations(body, *place);
111              if locations.len() == 0 {
112                log::warn!("FakeRead of {place:?} has no assignments");
113                return;
114              }
115              locations
116            }
117          },
118          _ => {
119            return;
120          }
121        };
122        (source_info.span, locations)
123      }
124      PlaceContext::NonUse(NonUseContext::VarDebugInfo)
125        if body.args_iter().any(|local| local == place.local) =>
126      {
127        let source_info = body.local_decls()[place.local].source_info;
128        let location = match LocationOrArg::from_place(*place, body) {
129          Some(arg_location) => arg_location,
130          None => LocationOrArg::Location(location),
131        };
132        (source_info.span, smallvec![location])
133      }
134      _ => {
135        return;
136      }
137    };
138
139    let span = try_span!(self, span);
140
141    let spanned_place = MirSpannedPlace {
142      place: *place,
143      locations,
144      span: span.data(),
145    };
146    trace!("spanned place: {spanned_place:?}");
147
148    self.0.mir_spans.push(spanned_place);
149  }
150
151  // The visit_statement and visit_terminator cases are a backup.
152  // Eg in the static_method case, if you have x = Foo::bar(), then
153  // then a slice on Foo::bar() would correspond to no places. The best we
154  // can do is at least make the slice on x.
155  fn visit_statement(
156    &mut self,
157    statement: &mir::Statement<'tcx>,
158    location: mir::Location,
159  ) {
160    self.super_statement(statement, location);
161
162    if let mir::StatementKind::Assign(box (lhs, _)) = &statement.kind {
163      if lhs.ty(self.1.local_decls(), self.0.tcx).ty.is_unit() {
164        return;
165      }
166
167      let span = try_span!(self, statement.source_info.span);
168      let spanned_place = MirSpannedPlace {
169        place: *lhs,
170        locations: smallvec![LocationOrArg::Location(location)],
171        span: span.data(),
172      };
173      trace!("spanned place (assign): {spanned_place:?}");
174      self.0.mir_spans.push(spanned_place);
175    }
176  }
177
178  fn visit_terminator(
179    &mut self,
180    terminator: &mir::Terminator<'tcx>,
181    location: mir::Location,
182  ) {
183    self.super_terminator(terminator, location);
184
185    let place = match &terminator.kind {
186      mir::TerminatorKind::Call { destination, .. } => *destination,
187      _ => {
188        return;
189      }
190    };
191
192    let span = try_span!(self, terminator.source_info.span);
193    let spanned_place = MirSpannedPlace {
194      place,
195      locations: smallvec![LocationOrArg::Location(location)],
196      span: span.data(),
197    };
198    trace!("spanned place (terminator): {spanned_place:?}");
199    self.0.mir_spans.push(spanned_place);
200  }
201}
202
203fn assigning_locations<'tcx>(
204  body: &Body<'tcx>,
205  place: mir::Place<'tcx>,
206) -> SmallVec<[LocationOrArg; 1]> {
207  body
208    .all_locations()
209    .filter(|location| match body.stmt_at(*location) {
210      Either::Left(Statement {
211        kind: StatementKind::Assign(box (lhs, _)),
212        ..
213      })
214      | Either::Right(Terminator {
215        kind: TerminatorKind::Call {
216          destination: lhs, ..
217        },
218        ..
219      }) => *lhs == place,
220      _ => false,
221    })
222    .map(LocationOrArg::Location)
223    .collect::<SmallVec<_>>()
224}