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 &outlives_constraint,
51 point,
52 universal_regions,
53 )
54 } else {
55 assert_eq!(location.statement_index, body[location.block].statements.len());
56 let terminator = body[location.block].terminator();
57 localize_terminator_constraint(
58 tcx,
59 body,
60 terminator,
61 liveness,
62 &outlives_constraint,
63 point,
64 universal_regions,
65 )
66 };
67 localized_outlives_constraints.push(localized_constraint);
68 }
69 }
70 }
71}
72
73/// For a given outlives constraint arising from a MIR statement, localize the constraint with the
74/// needed CFG `from`-`to` intra-block nodes.
75fn localize_statement_constraint<'tcx>(
76 tcx: TyCtxt<'tcx>,
77 body: &Body<'tcx>,
78 stmt: &Statement<'tcx>,
79 outlives_constraint: &OutlivesConstraint<'tcx>,
80 current_point: PointIndex,
81 universal_regions: &UniversalRegions<'tcx>,
82) -> LocalizedOutlivesConstraint {
83 match &stmt.kind {
84 StatementKind::Assign(box (lhs, rhs)) => {
85 // To create localized outlives constraints without midpoints, we rely on the property
86 // that no input regions from the RHS of the assignment will flow into themselves: they
87 // should not appear in the output regions in the LHS. We believe this to be true by
88 // construction of the MIR, via temporaries, and assert it here.
89 //
90 // We think we don't need midpoints because:
91 // - every LHS Place has a unique set of regions that don't appear elsewhere
92 // - this implies that for them to be part of the RHS, the same Place must be read and
93 // written
94 // - and that should be impossible in MIR
95 //
96 // When we have a more complete implementation in the future, tested with crater, etc,
97 // we can remove this assertion. It's a debug assert because it can be expensive.
98 debug_assert!(
99 {
100 let mut lhs_regions = FxHashSet::default();
101 tcx.for_each_free_region(lhs, |region| {
102 let region = universal_regions.to_region_vid(region);
103 lhs_regions.insert(region);
104 });
105
106 let mut rhs_regions = FxHashSet::default();
107 tcx.for_each_free_region(rhs, |region| {
108 let region = universal_regions.to_region_vid(region);
109 rhs_regions.insert(region);
110 });
111
112 // The intersection between LHS and RHS regions should be empty.
113 lhs_regions.is_disjoint(&rhs_regions)
114 },
115 "there should be no common regions between the LHS and RHS of an assignment"
116 );
117
118 let lhs_ty = body.local_decls[lhs.local].ty;
119 let successor_point = current_point;
120 compute_constraint_direction(
121 tcx,
122 outlives_constraint,
123 &lhs_ty,
124 current_point,
125 successor_point,
126 universal_regions,
127 )
128 }
129 _ => {
130 // For the other cases, we localize an outlives constraint to where it arises.
131 LocalizedOutlivesConstraint {
132 source: outlives_constraint.sup,
133 from: current_point,
134 target: outlives_constraint.sub,
135 to: current_point,
136 }
137 }
138 }
139}
140
141/// For a given outlives constraint arising from a MIR terminator, localize the constraint with the
142/// needed CFG `from`-`to` inter-block nodes.
143fn localize_terminator_constraint<'tcx>(
144 tcx: TyCtxt<'tcx>,
145 body: &Body<'tcx>,
146 terminator: &Terminator<'tcx>,
147 liveness: &LivenessValues,
148 outlives_constraint: &OutlivesConstraint<'tcx>,
149 current_point: PointIndex,
150 universal_regions: &UniversalRegions<'tcx>,
151) -> LocalizedOutlivesConstraint {
152 // FIXME: check if other terminators need the same handling as `Call`s, in particular
153 // Assert/Yield/Drop. A handful of tests are failing with Drop related issues, as well as some
154 // coroutine tests, and that may be why.
155 match &terminator.kind {
156 // FIXME: also handle diverging calls.
157 TerminatorKind::Call { destination, target: Some(target), .. } => {
158 // Calls are similar to assignments, and thus follow the same pattern. If there is a
159 // target for the call we also relate what flows into the destination here to entry to
160 // that successor.
161 let destination_ty = destination.ty(&body.local_decls, tcx);
162 let successor_location = Location { block: *target, statement_index: 0 };
163 let successor_point = liveness.point_from_location(successor_location);
164 compute_constraint_direction(
165 tcx,
166 outlives_constraint,
167 &destination_ty,
168 current_point,
169 successor_point,
170 universal_regions,
171 )
172 }
173 _ => {
174 // Typeck constraints guide loans between regions at the current point, so we do that in
175 // the general case, and liveness will take care of making them flow to the terminator's
176 // successors.
177 LocalizedOutlivesConstraint {
178 source: outlives_constraint.sup,
179 from: current_point,
180 target: outlives_constraint.sub,
181 to: current_point,
182 }
183 }
184 }
185}
186
187/// For a given outlives constraint and CFG edge, returns the localized constraint with the
188/// appropriate `from`-`to` direction. This is computed according to whether the constraint flows to
189/// or from a free region in the given `value`, some kind of result for an effectful operation, like
190/// the LHS of an assignment.
191fn compute_constraint_direction<'tcx>(
192 tcx: TyCtxt<'tcx>,
193 outlives_constraint: &OutlivesConstraint<'tcx>,
194 value: &impl TypeVisitable<TyCtxt<'tcx>>,
195 current_point: PointIndex,
196 successor_point: PointIndex,
197 universal_regions: &UniversalRegions<'tcx>,
198) -> LocalizedOutlivesConstraint {
199 let mut to = current_point;
200 let mut from = current_point;
201 tcx.for_each_free_region(value, |region| {
202 let region = universal_regions.to_region_vid(region);
203 if region == outlives_constraint.sub {
204 // This constraint flows into the result, its effects start becoming visible on exit.
205 to = successor_point;
206 } else if region == outlives_constraint.sup {
207 // This constraint flows from the result, its effects start becoming visible on exit.
208 from = successor_point;
209 }
210 });
211
212 LocalizedOutlivesConstraint {
213 source: outlives_constraint.sup,
214 from,
215 target: outlives_constraint.sub,
216 to,
217 }
218}