rustc_borrowck/region_infer/
graphviz.rs

1//! This module provides linkage between RegionInferenceContext and
2//! `rustc_graphviz` traits, specialized to attaching borrowck analysis
3//! data to rendered labels.
4
5use std::borrow::Cow;
6use std::io::{self, Write};
7
8use itertools::Itertools;
9use rustc_graphviz as dot;
10use rustc_middle::ty::UniverseIndex;
11
12use super::*;
13
14fn render_outlives_constraint(constraint: &OutlivesConstraint<'_>) -> String {
15    match constraint.locations {
16        Locations::All(_) => "All(...)".to_string(),
17        Locations::Single(loc) => format!("{loc:?}"),
18    }
19}
20
21fn render_universe(u: UniverseIndex) -> String {
22    if u.is_root() {
23        return "".to_string();
24    }
25
26    format!("/{:?}", u)
27}
28
29fn render_region_vid<'tcx>(
30    tcx: TyCtxt<'tcx>,
31    rvid: RegionVid,
32    regioncx: &RegionInferenceContext<'tcx>,
33) -> String {
34    let universe_str = render_universe(regioncx.region_definition(rvid).universe);
35
36    let external_name_str = if let Some(external_name) =
37        regioncx.region_definition(rvid).external_name.and_then(|e| e.get_name(tcx))
38    {
39        format!(" ({external_name})")
40    } else {
41        "".to_string()
42    };
43
44    let extra_info = match regioncx.region_definition(rvid).origin {
45        NllRegionVariableOrigin::FreeRegion => "".to_string(),
46        NllRegionVariableOrigin::Placeholder(p) => match p.bound.kind {
47            ty::BoundRegionKind::Named(def_id) => {
48                format!(" (for<{}>)", tcx.item_name(def_id))
49            }
50            ty::BoundRegionKind::ClosureEnv | ty::BoundRegionKind::Anon => " (for<'_>)".to_string(),
51            ty::BoundRegionKind::NamedAnon(_) => {
52                bug!("only used for pretty printing")
53            }
54        },
55        NllRegionVariableOrigin::Existential { name: Some(name), .. } => format!(" (ex<{name}>)"),
56        NllRegionVariableOrigin::Existential { .. } => format!(" (ex<'_>)"),
57    };
58
59    format!("{:?}{universe_str}{external_name_str}{extra_info}", rvid)
60}
61
62impl<'tcx> RegionInferenceContext<'tcx> {
63    /// Write out the region constraint graph.
64    pub(crate) fn dump_graphviz_raw_constraints(
65        &self,
66        tcx: TyCtxt<'tcx>,
67        mut w: &mut dyn Write,
68    ) -> io::Result<()> {
69        dot::render(&RawConstraints { tcx, regioncx: self }, &mut w)
70    }
71
72    /// Write out the region constraint SCC graph.
73    pub(crate) fn dump_graphviz_scc_constraints(
74        &self,
75        tcx: TyCtxt<'tcx>,
76        mut w: &mut dyn Write,
77    ) -> io::Result<()> {
78        let mut nodes_per_scc: IndexVec<ConstraintSccIndex, _> =
79            self.constraint_sccs.all_sccs().map(|_| Vec::new()).collect();
80
81        for region in self.definitions.indices() {
82            let scc = self.constraint_sccs.scc(region);
83            nodes_per_scc[scc].push(region);
84        }
85
86        dot::render(&SccConstraints { tcx, regioncx: self, nodes_per_scc }, &mut w)
87    }
88}
89
90struct RawConstraints<'a, 'tcx> {
91    tcx: TyCtxt<'tcx>,
92    regioncx: &'a RegionInferenceContext<'tcx>,
93}
94
95impl<'a, 'this, 'tcx> dot::Labeller<'this> for RawConstraints<'a, 'tcx> {
96    type Node = RegionVid;
97    type Edge = OutlivesConstraint<'tcx>;
98
99    fn graph_id(&'this self) -> dot::Id<'this> {
100        dot::Id::new("RegionInferenceContext").unwrap()
101    }
102    fn node_id(&'this self, n: &RegionVid) -> dot::Id<'this> {
103        dot::Id::new(format!("r{}", n.index())).unwrap()
104    }
105    fn node_shape(&'this self, _node: &RegionVid) -> Option<dot::LabelText<'this>> {
106        Some(dot::LabelText::LabelStr(Cow::Borrowed("box")))
107    }
108    fn node_label(&'this self, n: &RegionVid) -> dot::LabelText<'this> {
109        dot::LabelText::LabelStr(render_region_vid(self.tcx, *n, self.regioncx).into())
110    }
111    fn edge_label(&'this self, e: &OutlivesConstraint<'tcx>) -> dot::LabelText<'this> {
112        dot::LabelText::LabelStr(render_outlives_constraint(e).into())
113    }
114}
115
116impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for RawConstraints<'a, 'tcx> {
117    type Node = RegionVid;
118    type Edge = OutlivesConstraint<'tcx>;
119
120    fn nodes(&'this self) -> dot::Nodes<'this, RegionVid> {
121        let vids: Vec<RegionVid> = self.regioncx.definitions.indices().collect();
122        vids.into()
123    }
124    fn edges(&'this self) -> dot::Edges<'this, OutlivesConstraint<'tcx>> {
125        (&self.regioncx.constraints.outlives().raw[..]).into()
126    }
127
128    // Render `a: b` as `a -> b`, indicating the flow
129    // of data during inference.
130
131    fn source(&'this self, edge: &OutlivesConstraint<'tcx>) -> RegionVid {
132        edge.sup
133    }
134
135    fn target(&'this self, edge: &OutlivesConstraint<'tcx>) -> RegionVid {
136        edge.sub
137    }
138}
139
140struct SccConstraints<'a, 'tcx> {
141    tcx: TyCtxt<'tcx>,
142    regioncx: &'a RegionInferenceContext<'tcx>,
143    nodes_per_scc: IndexVec<ConstraintSccIndex, Vec<RegionVid>>,
144}
145
146impl<'a, 'this, 'tcx> dot::Labeller<'this> for SccConstraints<'a, 'tcx> {
147    type Node = ConstraintSccIndex;
148    type Edge = (ConstraintSccIndex, ConstraintSccIndex);
149
150    fn graph_id(&'this self) -> dot::Id<'this> {
151        dot::Id::new("RegionInferenceContext".to_string()).unwrap()
152    }
153    fn node_id(&'this self, n: &ConstraintSccIndex) -> dot::Id<'this> {
154        dot::Id::new(format!("r{}", n.index())).unwrap()
155    }
156    fn node_shape(&'this self, _node: &ConstraintSccIndex) -> Option<dot::LabelText<'this>> {
157        Some(dot::LabelText::LabelStr(Cow::Borrowed("box")))
158    }
159    fn node_label(&'this self, n: &ConstraintSccIndex) -> dot::LabelText<'this> {
160        let nodes_str = self.nodes_per_scc[*n]
161            .iter()
162            .map(|n| render_region_vid(self.tcx, *n, self.regioncx))
163            .join(", ");
164        dot::LabelText::LabelStr(format!("SCC({n}) = {{{nodes_str}}}", n = n.as_usize()).into())
165    }
166}
167
168impl<'a, 'this, 'tcx> dot::GraphWalk<'this> for SccConstraints<'a, 'tcx> {
169    type Node = ConstraintSccIndex;
170    type Edge = (ConstraintSccIndex, ConstraintSccIndex);
171
172    fn nodes(&'this self) -> dot::Nodes<'this, ConstraintSccIndex> {
173        let vids: Vec<ConstraintSccIndex> = self.regioncx.constraint_sccs.all_sccs().collect();
174        vids.into()
175    }
176    fn edges(&'this self) -> dot::Edges<'this, (ConstraintSccIndex, ConstraintSccIndex)> {
177        let edges: Vec<_> = self
178            .regioncx
179            .constraint_sccs
180            .all_sccs()
181            .flat_map(|scc_a| {
182                self.regioncx
183                    .constraint_sccs
184                    .successors(scc_a)
185                    .iter()
186                    .map(move |&scc_b| (scc_a, scc_b))
187            })
188            .collect();
189
190        edges.into()
191    }
192
193    // Render `a: b` as `a -> b`, indicating the flow
194    // of data during inference.
195
196    fn source(&'this self, edge: &(ConstraintSccIndex, ConstraintSccIndex)) -> ConstraintSccIndex {
197        edge.0
198    }
199
200    fn target(&'this self, edge: &(ConstraintSccIndex, ConstraintSccIndex)) -> ConstraintSccIndex {
201        edge.1
202    }
203}