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}