oxc_cfg/
dot.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Debug, Display},
4};
5
6use itertools::Itertools as _;
7use petgraph::{
8    dot::{Config, Dot},
9    visit::EdgeRef,
10};
11use rustc_hash::FxHashMap;
12
13use super::IterationInstructionKind;
14use crate::{
15    BasicBlock, ControlFlowGraph, EdgeType, Instruction, InstructionKind, LabeledInstruction,
16    ReturnInstructionKind,
17};
18
19pub trait DisplayDot {
20    fn display_dot(&self) -> String;
21}
22
23impl DisplayDot for ControlFlowGraph {
24    fn display_dot(&self) -> String {
25        format!(
26            "{:?}",
27            Dot::with_attr_getters(
28                &self.graph,
29                &[Config::EdgeNoLabel, Config::NodeNoLabel],
30                &|_graph, edge| {
31                    let weight = edge.weight();
32                    let mut attrs = Attrs::default().with("label", format!("{weight:?}"));
33
34                    if matches!(weight, EdgeType::Unreachable)
35                        || self.basic_block(edge.source()).is_unreachable()
36                    {
37                        attrs += ("style", "dotted");
38                    } else if matches!(weight, EdgeType::Error(_)) {
39                        attrs += ("color", "red");
40                    }
41
42                    format!("{attrs:?}")
43                },
44                &|_graph, node| {
45                    let block = &self.basic_blocks[*node.1];
46                    let mut attrs = Attrs::default().with("label", block.display_dot());
47
48                    if node.1.index() == 0 {
49                        attrs += ("color", "green");
50                    }
51                    if block.is_unreachable() {
52                        attrs += ("style", "dotted");
53                    }
54
55                    format!("{attrs:?}")
56                },
57            )
58        )
59    }
60}
61
62impl DisplayDot for BasicBlock {
63    fn display_dot(&self) -> String {
64        self.instructions().iter().map(DisplayDot::display_dot).join("\n")
65    }
66}
67
68impl DisplayDot for Instruction {
69    fn display_dot(&self) -> String {
70        match self.kind {
71            InstructionKind::Statement => "statement",
72            InstructionKind::Unreachable => "unreachable",
73            InstructionKind::Throw => "throw",
74            InstructionKind::Condition => "condition",
75            InstructionKind::Iteration(IterationInstructionKind::Of) => "iteration <of>",
76            InstructionKind::Iteration(IterationInstructionKind::In) => "iteration <in>",
77            InstructionKind::Break(LabeledInstruction::Labeled) => "break <label>",
78            InstructionKind::Break(LabeledInstruction::Unlabeled) => "break",
79            InstructionKind::Continue(LabeledInstruction::Labeled) => "continue <label>",
80            InstructionKind::Continue(LabeledInstruction::Unlabeled) => "continue",
81            InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined) => {
82                "return <implicit undefined>"
83            }
84            InstructionKind::ImplicitReturn => "return",
85            InstructionKind::Return(ReturnInstructionKind::NotImplicitUndefined) => {
86                "return <value>"
87            }
88        }
89        .to_string()
90    }
91}
92
93#[derive(Clone)]
94pub enum Attr<'a> {
95    String(Cow<'a, str>),
96    Identifier(Cow<'a, str>),
97    Int(i64),
98}
99impl<'a> Attr<'a> {
100    #[inline]
101    #[must_use]
102    pub fn ident<S>(identifier: S) -> Self
103    where
104        S: Into<Cow<'a, str>>,
105    {
106        Self::Identifier(identifier.into())
107    }
108}
109
110impl Debug for Attr<'_> {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            Self::Int(i) => Display::fmt(i, f),
114            Self::String(s) => Debug::fmt(s, f),
115            Self::Identifier(ident) => Display::fmt(ident, f), // Display instead of Debug
116        }
117    }
118}
119
120impl<'a> From<&'a str> for Attr<'a> {
121    fn from(value: &'a str) -> Self {
122        Self::String(Cow::Borrowed(value))
123    }
124}
125
126impl From<String> for Attr<'static> {
127    fn from(value: String) -> Self {
128        Self::String(Cow::Owned(value))
129    }
130}
131
132impl From<i64> for Attr<'_> {
133    fn from(value: i64) -> Self {
134        Self::Int(value)
135    }
136}
137
138#[derive(Default)]
139pub struct Attrs<'a>(FxHashMap<Cow<'a, str>, Attr<'a>>);
140impl<'a> Attrs<'a> {
141    #[must_use]
142    #[inline]
143    pub fn with<K, V>(mut self, key: K, value: V) -> Self
144    where
145        K: Into<Cow<'static, str>>,
146        V: Into<Attr<'a>>,
147    {
148        self += (key, value);
149        self
150    }
151}
152
153impl<'a, K, V> FromIterator<(K, V)> for Attrs<'a>
154where
155    K: Into<Cow<'static, str>>,
156    V: Into<Attr<'a>>,
157{
158    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
159        Self(iter.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
160    }
161}
162
163impl<'a, K, V> std::ops::AddAssign<(K, V)> for Attrs<'a>
164where
165    K: Into<Cow<'static, str>>,
166    V: Into<Attr<'a>>,
167{
168    fn add_assign(&mut self, (key, value): (K, V)) {
169        self.0.insert(key.into(), value.into());
170    }
171}
172
173impl Debug for Attrs<'_> {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        if self.0.is_empty() {
176            return Ok(());
177        }
178
179        for (i, (k, v)) in self.0.iter().enumerate() {
180            if i == 0 {
181                write!(f, "{k}={v:?}")?;
182            } else {
183                write!(f, ", {k}={v:?}")?;
184            }
185        }
186
187        Ok(())
188    }
189}