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