rustc_borrowck/polonius/typeck_constraints.rs
1use rustc_data_structures::fx::FxHashSet;
2use rustc_middle::mir::{Body, Location, Statement, StatementKind, Terminator, TerminatorKind};
3use rustc_middle::ty::{TyCtxt, TypeVisitable};
4use rustc_mir_dataflow::points::PointIndex;
5
6use super::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet};
7use crate::constraints::OutlivesConstraint;
8use crate::region_infer::values::LivenessValues;
9use crate::type_check::Locations;
10use crate::universal_regions::UniversalRegions;
11
12/// Propagate loans throughout the subset graph at a given point (with some subtleties around the
13/// location where effects start to be visible).
14pub(super) fn convert_typeck_constraints<'tcx>(
15 tcx: TyCtxt<'tcx>,
16 body: &Body<'tcx>,
17 liveness: &LivenessValues,
18 outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
19 universal_regions: &UniversalRegions<'tcx>,
20 localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
21) {
22 for outlives_constraint in outlives_constraints {
23 match outlives_constraint.locations {
24 Locations::All(_) => {
25 // We don't turn constraints holding at all points into physical edges at every
26 // point in the graph. They are encoded into *traversal* instead: a given node's
27 // successors will combine these logical edges with the regular, physical, localized
28 // edges.
29 continue;
30 }
31
32 Locations::Single(location) => {
33 // This constraint is marked as holding at one location, we localize it to that
34 // location or its successor, depending on the corresponding MIR
35 // statement/terminator. Unfortunately, they all show up from typeck as coming "on
36 // entry", so for now we modify them to take effects that should apply "on exit"
37 // into account.
38 //
39 // FIXME: this approach is subtle, complicated, and hard to test, so we should track
40 // this information better in MIR typeck instead, for example with a new `Locations`
41 // variant that contains which node is crossing over between entry and exit.
42 let point = liveness.point_from_location(location);
43 let localized_constraint = if let Some(stmt) =
44 body[location.block].statements.get(location.statement_index)
45 {
46 localize_statement_constraint(
47 tcx,
48 body,
49 stmt,
50 liveness,
51 &outlives_constraint,
52 location,
53 point,
54 universal_regions,
55 )
56 } else {
57 assert_eq!(location.statement_index, body[location.block].statements.len());
58 let terminator = body[location.block].terminator();
59 localize_terminator_constraint(
60 tcx,
61 body,
62 terminator,
63 liveness,
64 &outlives_constraint,
65 point,
66 universal_regions,
67 )
68 };
69 localized_outlives_constraints.push(localized_constraint);
70 }
71 }
72 }
73}
74
75/// For a given outlives constraint arising from a MIR statement, localize the constraint with the
76/// needed CFG `from`-`to` intra-block nodes.
77fn localize_statement_constraint<'tcx>(
78 tcx: TyCtxt<'tcx>,
79 body: &Body<'tcx>,
80 stmt: &Statement<'tcx>,
81 liveness: &LivenessValues,
82 outlives_constraint: &OutlivesConstraint<'tcx>,
83 current_location: Location,
84 current_point: PointIndex,
85 universal_regions: &UniversalRegions<'tcx>,
86) -> LocalizedOutlivesConstraint {
87 match &stmt.kind {
88 StatementKind::Assign(box (lhs, rhs)) => {
89 // To create localized outlives constraints without midpoints, we rely on the property
90 // that no input regions from the RHS of the assignment will flow into themselves: they
91 // should not appear in the output regions in the LHS. We believe this to be true by
92 // construction of the MIR, via temporaries, and assert it here.
93 //
94 // We think we don't need midpoints because:
95 // - every LHS Place has a unique set of regions that don't appear elsewhere
96 // - this implies that for them to be part of the RHS, the same Place must be read and
97 // written
98 // - and that should be impossible in MIR
99 //
100 // When we have a more complete implementation in the future, tested with crater, etc,
101 // we can relax this to a debug assert instead, or remove it.
102 assert!(
103 {
104 let mut lhs_regions = FxHashSet::default();
105 tcx.for_each_free_region(lhs, |region| {
106 let region = universal_regions.to_region_vid(region);
107 lhs_regions.insert(region);
108 });
109
110 let mut rhs_regions = FxHashSet::default();
111 tcx.for_each_free_region(rhs, |region| {
112 let region = universal_regions.to_region_vid(region);
113 rhs_regions.insert(region);
114 });
115
116 // The intersection between LHS and RHS regions should be empty.
117 lhs_regions.is_disjoint(&rhs_regions)
118 },
119 "there should be no common regions between the LHS and RHS of an assignment"
120 );
121
122 // As mentioned earlier, we should be tracking these better upstream but: we want to
123 // relate the types on entry to the type of the place on exit. That is, outlives
124 // constraints on the RHS are on entry, and outlives constraints to/from the LHS are on
125 // exit (i.e. on entry to the successor location).
126 let lhs_ty = body.local_decls[lhs.local].ty;
127 let successor_location = Location {
128 block: current_location.block,
129 statement_index: current_location.statement_index + 1,
130 };
131 let successor_point = liveness.point_from_location(successor_location);
132 compute_constraint_direction(
133 tcx,
134 outlives_constraint,
135 &lhs_ty,
136 current_point,
137 successor_point,
138 universal_regions,
139 )
140 }
141 _ => {
142 // For the other cases, we localize an outlives constraint to where it arises.
143 LocalizedOutlivesConstraint {
144 source: outlives_constraint.sup,
145 from: current_point,
146 target: outlives_constraint.sub,
147 to: current_point,
148 }
149 }
150 }
151}
152
153/// For a given outlives constraint arising from a MIR terminator, localize the constraint with the
154/// needed CFG `from`-`to` inter-block nodes.
155fn localize_terminator_constraint<'tcx>(
156 tcx: TyCtxt<'tcx>,
157 body: &Body<'tcx>,
158 terminator: &Terminator<'tcx>,
159 liveness: &LivenessValues,
160 outlives_constraint: &OutlivesConstraint<'tcx>,
161 current_point: PointIndex,
162 universal_regions: &UniversalRegions<'tcx>,
163) -> LocalizedOutlivesConstraint {
164 // FIXME: check if other terminators need the same handling as `Call`s, in particular
165 // Assert/Yield/Drop. A handful of tests are failing with Drop related issues, as well as some
166 // coroutine tests, and that may be why.
167 match &terminator.kind {
168 // FIXME: also handle diverging calls.
169 TerminatorKind::Call { destination, target: Some(target), .. } => {
170 // Calls are similar to assignments, and thus follow the same pattern. If there is a
171 // target for the call we also relate what flows into the destination here to entry to
172 // that successor.
173 let destination_ty = destination.ty(&body.local_decls, tcx);
174 let successor_location = Location { block: *target, statement_index: 0 };
175 let successor_point = liveness.point_from_location(successor_location);
176 compute_constraint_direction(
177 tcx,
178 outlives_constraint,
179 &destination_ty,
180 current_point,
181 successor_point,
182 universal_regions,
183 )
184 }
185 _ => {
186 // Typeck constraints guide loans between regions at the current point, so we do that in
187 // the general case, and liveness will take care of making them flow to the terminator's
188 // successors.
189 LocalizedOutlivesConstraint {
190 source: outlives_constraint.sup,
191 from: current_point,
192 target: outlives_constraint.sub,
193 to: current_point,
194 }
195 }
196 }
197}
198/// For a given outlives constraint and CFG edge, returns the localized constraint with the
199/// appropriate `from`-`to` direction. This is computed according to whether the constraint flows to
200/// or from a free region in the given `value`, some kind of result for an effectful operation, like
201/// the LHS of an assignment.
202fn compute_constraint_direction<'tcx>(
203 tcx: TyCtxt<'tcx>,
204 outlives_constraint: &OutlivesConstraint<'tcx>,
205 value: &impl TypeVisitable<TyCtxt<'tcx>>,
206 current_point: PointIndex,
207 successor_point: PointIndex,
208 universal_regions: &UniversalRegions<'tcx>,
209) -> LocalizedOutlivesConstraint {
210 let mut to = current_point;
211 let mut from = current_point;
212 tcx.for_each_free_region(value, |region| {
213 let region = universal_regions.to_region_vid(region);
214 if region == outlives_constraint.sub {
215 // This constraint flows into the result, its effects start becoming visible on exit.
216 to = successor_point;
217 } else if region == outlives_constraint.sup {
218 // This constraint flows from the result, its effects start becoming visible on exit.
219 from = successor_point;
220 }
221 });
222
223 LocalizedOutlivesConstraint {
224 source: outlives_constraint.sup,
225 from,
226 target: outlives_constraint.sub,
227 to,
228 }
229}