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}