]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_mir/src/dataflow/framework/graphviz.rs
New upstream version 1.49.0~beta.4+dfsg1
[rustc.git] / compiler / rustc_mir / src / dataflow / framework / graphviz.rs
CommitLineData
dfeec247
XL
1//! A helpful diagram for debugging dataflow problems.
2
1b1a35ee
XL
3use std::borrow::Cow;
4use std::lazy::SyncOnceCell;
dfeec247 5use std::{io, ops, str};
e74abb32 6
1b1a35ee 7use regex::Regex;
f035d41b 8use rustc_graphviz as dot;
ba9703b0 9use rustc_middle::mir::{self, BasicBlock, Body, Location};
e74abb32 10
1b1a35ee
XL
11use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
12use super::{Analysis, Direction, Results, ResultsRefCursor, ResultsVisitor};
e74abb32 13use crate::util::graphviz_safe_def_name;
e74abb32 14
1b1a35ee
XL
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum OutputStyle {
17 AfterOnly,
18 BeforeAndAfter,
19}
20
21impl OutputStyle {
22 fn num_state_columns(&self) -> usize {
23 match self {
24 Self::AfterOnly => 1,
25 Self::BeforeAndAfter => 2,
26 }
27 }
28}
29
e74abb32
XL
30pub struct Formatter<'a, 'tcx, A>
31where
32 A: Analysis<'tcx>,
33{
34 body: &'a Body<'tcx>,
1b1a35ee
XL
35 results: &'a Results<'tcx, A>,
36 style: OutputStyle,
e74abb32
XL
37}
38
39impl<A> Formatter<'a, 'tcx, A>
40where
41 A: Analysis<'tcx>,
42{
29967ef6
XL
43 pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>, style: OutputStyle) -> Self {
44 Formatter { body, results, style }
e74abb32
XL
45 }
46}
47
48/// A pair of a basic block and an index into that basic blocks `successors`.
49#[derive(Copy, Clone, PartialEq, Eq, Debug)]
50pub struct CfgEdge {
51 source: BasicBlock,
52 index: usize,
53}
54
f9f354fc 55fn dataflow_successors(body: &Body<'tcx>, bb: BasicBlock) -> Vec<CfgEdge> {
e74abb32
XL
56 body[bb]
57 .terminator()
58 .successors()
59 .enumerate()
60 .map(|(index, _)| CfgEdge { source: bb, index })
61 .collect()
62}
63
64impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
65where
66 A: Analysis<'tcx>,
1b1a35ee 67 A::Domain: DebugWithContext<A>,
e74abb32
XL
68{
69 type Node = BasicBlock;
70 type Edge = CfgEdge;
71
72 fn graph_id(&self) -> dot::Id<'_> {
29967ef6 73 let name = graphviz_safe_def_name(self.body.source.def_id());
e74abb32
XL
74 dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
75 }
76
77 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
78 dot::Id::new(format!("bb_{}", n.index())).unwrap()
79 }
80
81 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
82 let mut label = Vec::new();
1b1a35ee
XL
83 let mut fmt = BlockFormatter {
84 results: ResultsRefCursor::new(self.body, self.results),
85 style: self.style,
86 bg: Background::Light,
87 };
88
89 fmt.write_node_label(&mut label, self.body, *block).unwrap();
e74abb32
XL
90 dot::LabelText::html(String::from_utf8(label).unwrap())
91 }
92
93 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
94 Some(dot::LabelText::label("none"))
95 }
96
97 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
dfeec247 98 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
e74abb32
XL
99 dot::LabelText::label(label.clone())
100 }
101}
102
103impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
104where
105 A: Analysis<'tcx>,
106{
107 type Node = BasicBlock;
108 type Edge = CfgEdge;
109
110 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
dfeec247 111 self.body.basic_blocks().indices().collect::<Vec<_>>().into()
e74abb32
XL
112 }
113
114 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
115 self.body
116 .basic_blocks()
117 .indices()
f9f354fc 118 .flat_map(|bb| dataflow_successors(self.body, bb))
e74abb32
XL
119 .collect::<Vec<_>>()
120 .into()
121 }
122
123 fn source(&self, edge: &Self::Edge) -> Self::Node {
124 edge.source
125 }
126
127 fn target(&self, edge: &Self::Edge) -> Self::Node {
dfeec247 128 self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap()
e74abb32
XL
129 }
130}
131
132struct BlockFormatter<'a, 'tcx, A>
133where
134 A: Analysis<'tcx>,
135{
e74abb32
XL
136 results: ResultsRefCursor<'a, 'a, 'tcx, A>,
137 bg: Background,
1b1a35ee 138 style: OutputStyle,
e74abb32
XL
139}
140
141impl<A> BlockFormatter<'a, 'tcx, A>
142where
143 A: Analysis<'tcx>,
1b1a35ee 144 A::Domain: DebugWithContext<A>,
e74abb32 145{
dfeec247
XL
146 const HEADER_COLOR: &'static str = "#a0a0a0";
147
e74abb32
XL
148 fn toggle_background(&mut self) -> Background {
149 let bg = self.bg;
150 self.bg = !bg;
151 bg
152 }
153
154 fn write_node_label(
155 &mut self,
156 w: &mut impl io::Write,
157 body: &'a Body<'tcx>,
158 block: BasicBlock,
159 ) -> io::Result<()> {
160 // Sample output:
161 // +-+-----------------------------------------------+
162 // A | bb4 |
163 // +-+----------------------------------+------------+
164 // B | MIR | STATE |
165 // +-+----------------------------------+------------+
166 // C | | (on entry) | {_0,_2,_3} |
167 // +-+----------------------------------+------------+
168 // D |0| StorageLive(_7) | |
169 // +-+----------------------------------+------------+
170 // |1| StorageLive(_8) | |
171 // +-+----------------------------------+------------+
172 // |2| _8 = &mut _1 | +_8 |
173 // +-+----------------------------------+------------+
174 // E |T| _4 = const Foo::twiddle(move _2) | -_2 |
175 // +-+----------------------------------+------------+
176 // F | | (on unwind) | {_0,_3,_8} |
177 // +-+----------------------------------+------------+
178 // | | (on successful return) | +_4 |
179 // +-+----------------------------------+------------+
180
74b04a01
XL
181 // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
182 // children. This is because `xdot` seemed to have a hard time correctly propagating
183 // attributes. Make sure to test the output before trying to remove the redundancy.
184 // Notably, `align` was found to have no effect when applied only to <table>.
185
186 let table_fmt = concat!(
187 " border=\"1\"",
188 " cellborder=\"1\"",
189 " cellspacing=\"0\"",
190 " cellpadding=\"3\"",
191 " sides=\"rb\"",
192 );
193 write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
e74abb32 194
dfeec247 195 // A + B: Block header
1b1a35ee
XL
196 match self.style {
197 OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
198 OutputStyle::BeforeAndAfter => {
199 self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
200 }
dfeec247 201 }
e74abb32 202
f9f354fc 203 // C: State at start of block
e74abb32
XL
204 self.bg = Background::Light;
205 self.results.seek_to_block_start(block);
1b1a35ee 206 let block_start_state = self.results.get().clone();
f9f354fc 207 self.write_row_with_full_state(w, "", "(on start)")?;
e74abb32 208
1b1a35ee
XL
209 // D + E: Statement and terminator transfer functions
210 self.write_statements_and_terminator(w, body, block)?;
e74abb32 211
f9f354fc 212 // F: State at end of block
74b04a01 213
1b1a35ee
XL
214 let terminator = body[block].terminator();
215
74b04a01
XL
216 // Write the full dataflow state immediately after the terminator if it differs from the
217 // state at block entry.
f9f354fc 218 self.results.seek_to_block_end(block);
1b1a35ee 219 if self.results.get() != &block_start_state || A::Direction::is_backward() {
74b04a01
XL
220 let after_terminator_name = match terminator.kind {
221 mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
f9f354fc 222 _ => "(on end)",
74b04a01
XL
223 };
224
225 self.write_row_with_full_state(w, "", after_terminator_name)?;
e74abb32
XL
226 }
227
1b1a35ee
XL
228 // Write any changes caused by terminator-specific effects.
229 //
230 // FIXME: These should really be printed as part of each outgoing edge rather than the node
231 // for the basic block itself. That way, we could display terminator-specific effects for
232 // backward dataflow analyses as well as effects for `SwitchInt` terminators.
f9f354fc
XL
233 match terminator.kind {
234 mir::TerminatorKind::Call {
235 destination: Some((return_place, _)),
236 ref func,
237 ref args,
238 ..
239 } => {
240 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
f9f354fc
XL
241 let state_on_unwind = this.results.get().clone();
242 this.results.apply_custom_effect(|analysis, state| {
243 analysis.apply_call_return_effect(state, block, func, args, return_place);
244 });
245
1b1a35ee
XL
246 write!(
247 w,
248 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
249 colspan = this.style.num_state_columns(),
250 fmt = fmt,
251 diff = diff_pretty(
252 this.results.get(),
253 &state_on_unwind,
254 this.results.analysis()
255 ),
256 )
f9f354fc
XL
257 })?;
258 }
259
260 mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
261 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
f9f354fc
XL
262 let state_on_generator_drop = this.results.get().clone();
263 this.results.apply_custom_effect(|analysis, state| {
264 analysis.apply_yield_resume_effect(state, resume, resume_arg);
265 });
266
1b1a35ee 267 write!(
f9f354fc 268 w,
1b1a35ee
XL
269 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
270 colspan = this.style.num_state_columns(),
271 fmt = fmt,
272 diff = diff_pretty(
273 this.results.get(),
274 &state_on_generator_drop,
275 this.results.analysis()
276 ),
277 )
f9f354fc
XL
278 })?;
279 }
280
281 _ => {}
74b04a01
XL
282 };
283
e74abb32
XL
284 write!(w, "</table>")
285 }
286
dfeec247 287 fn write_block_header_simple(
e74abb32
XL
288 &mut self,
289 w: &mut impl io::Write,
dfeec247 290 block: BasicBlock,
e74abb32 291 ) -> io::Result<()> {
dfeec247
XL
292 // +-------------------------------------------------+
293 // A | bb4 |
294 // +-----------------------------------+-------------+
295 // B | MIR | STATE |
296 // +-+---------------------------------+-------------+
297 // | | ... | |
e74abb32 298
dfeec247
XL
299 // A
300 write!(
301 w,
302 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
303 block_id = block.index(),
304 )?;
e74abb32 305
dfeec247 306 // B
e74abb32
XL
307 write!(
308 w,
dfeec247
XL
309 concat!(
310 "<tr>",
311 r#"<td colspan="2" {fmt}>MIR</td>"#,
312 r#"<td {fmt}>STATE</td>"#,
313 "</tr>",
314 ),
315 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
e74abb32
XL
316 )
317 }
318
dfeec247 319 fn write_block_header_with_state_columns(
e74abb32
XL
320 &mut self,
321 w: &mut impl io::Write,
dfeec247 322 block: BasicBlock,
1b1a35ee 323 state_column_names: &[&str],
e74abb32 324 ) -> io::Result<()> {
dfeec247
XL
325 // +------------------------------------+-------------+
326 // A | bb4 | STATE |
327 // +------------------------------------+------+------+
328 // B | MIR | GEN | KILL |
329 // +-+----------------------------------+------+------+
330 // | | ... | | |
331
dfeec247
XL
332 // A
333 write!(
334 w,
335 concat!(
336 "<tr>",
337 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
338 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
339 "</tr>",
340 ),
341 fmt = "sides=\"tl\"",
342 num_state_cols = state_column_names.len(),
343 block_id = block.index(),
344 )?;
e74abb32 345
dfeec247
XL
346 // B
347 let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
348 write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
e74abb32 349
dfeec247
XL
350 for name in state_column_names {
351 write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
352 }
e74abb32 353
dfeec247
XL
354 write!(w, "</tr>")
355 }
356
1b1a35ee
XL
357 fn write_statements_and_terminator(
358 &mut self,
359 w: &mut impl io::Write,
360 body: &'a Body<'tcx>,
361 block: BasicBlock,
362 ) -> io::Result<()> {
363 let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style);
364
365 let mut befores = diffs.before.map(|v| v.into_iter());
366 let mut afters = diffs.after.into_iter();
367
368 let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
369 if A::Direction::is_forward() { it.next().unwrap() } else { it.next_back().unwrap() }
370 };
371
372 for (i, statement) in body[block].statements.iter().enumerate() {
373 let statement_str = format!("{:?}", statement);
374 let index_str = format!("{}", i);
375
376 let after = next_in_dataflow_order(&mut afters);
377 let before = befores.as_mut().map(next_in_dataflow_order);
378
379 self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
380 if let Some(before) = before {
381 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
382 }
383
384 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
385 })?;
386 }
387
388 let after = next_in_dataflow_order(&mut afters);
389 let before = befores.as_mut().map(next_in_dataflow_order);
390
391 assert!(afters.is_empty());
392 assert!(befores.as_ref().map_or(true, ExactSizeIterator::is_empty));
393
394 let terminator = body[block].terminator();
395 let mut terminator_str = String::new();
396 terminator.kind.fmt_head(&mut terminator_str).unwrap();
397
398 self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
399 if let Some(before) = before {
400 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
401 }
402
403 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
404 })
405 }
406
dfeec247
XL
407 /// Write a row with the given index and MIR, using the function argument to fill in the
408 /// "STATE" column(s).
409 fn write_row<W: io::Write>(
410 &mut self,
411 w: &mut W,
412 i: &str,
413 mir: &str,
414 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
415 ) -> io::Result<()> {
416 let bg = self.toggle_background();
74b04a01
XL
417 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
418
419 let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
e74abb32
XL
420
421 write!(
422 w,
dfeec247
XL
423 concat!(
424 "<tr>",
425 r#"<td {fmt} align="right">{i}</td>"#,
426 r#"<td {fmt} align="left">{mir}</td>"#,
427 ),
e74abb32 428 i = i,
dfeec247 429 fmt = fmt,
e74abb32
XL
430 mir = dot::escape_html(mir),
431 )?;
432
dfeec247
XL
433 f(self, w, &fmt)?;
434 write!(w, "</tr>")
435 }
436
437 fn write_row_with_full_state(
438 &mut self,
439 w: &mut impl io::Write,
440 i: &str,
441 mir: &str,
442 ) -> io::Result<()> {
443 self.write_row(w, i, mir, |this, w, fmt| {
444 let state = this.results.get();
445 let analysis = this.results.analysis();
446
1b1a35ee
XL
447 // FIXME: The full state vector can be quite long. It would be nice to split on commas
448 // and use some text wrapping algorithm.
e74abb32
XL
449 write!(
450 w,
1b1a35ee
XL
451 r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
452 colspan = this.style.num_state_columns(),
dfeec247 453 fmt = fmt,
1b1a35ee
XL
454 state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
455 )
dfeec247
XL
456 })
457 }
458}
459
1b1a35ee 460struct StateDiffCollector<'a, 'tcx, A>
dfeec247
XL
461where
462 A: Analysis<'tcx>,
463{
1b1a35ee
XL
464 analysis: &'a A,
465 prev_state: A::Domain,
466 before: Option<Vec<String>>,
467 after: Vec<String>,
dfeec247
XL
468}
469
1b1a35ee 470impl<A> StateDiffCollector<'a, 'tcx, A>
f9f354fc
XL
471where
472 A: Analysis<'tcx>,
1b1a35ee 473 A::Domain: DebugWithContext<A>,
f9f354fc 474{
1b1a35ee
XL
475 fn run(
476 body: &'a mir::Body<'tcx>,
477 block: BasicBlock,
478 results: &'a Results<'tcx, A>,
479 style: OutputStyle,
480 ) -> Self {
481 let mut collector = StateDiffCollector {
482 analysis: &results.analysis,
483 prev_state: results.analysis.bottom_value(body),
484 after: vec![],
485 before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
486 };
dfeec247 487
1b1a35ee
XL
488 results.visit_with(body, std::iter::once(block), &mut collector);
489 collector
dfeec247
XL
490 }
491}
492
1b1a35ee 493impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
dfeec247
XL
494where
495 A: Analysis<'tcx>,
1b1a35ee 496 A::Domain: DebugWithContext<A>,
dfeec247 497{
1b1a35ee 498 type FlowState = A::Domain;
dfeec247 499
1b1a35ee 500 fn visit_block_start(
dfeec247 501 &mut self,
1b1a35ee
XL
502 state: &Self::FlowState,
503 _block_data: &'mir mir::BasicBlockData<'tcx>,
504 _block: BasicBlock,
505 ) {
f9f354fc 506 if A::Direction::is_forward() {
1b1a35ee 507 self.prev_state.clone_from(state);
e74abb32 508 }
dfeec247 509 }
dfeec247 510
1b1a35ee
XL
511 fn visit_block_end(
512 &mut self,
513 state: &Self::FlowState,
514 _block_data: &'mir mir::BasicBlockData<'tcx>,
515 _block: BasicBlock,
516 ) {
517 if A::Direction::is_backward() {
518 self.prev_state.clone_from(state);
519 }
520 }
dfeec247 521
1b1a35ee
XL
522 fn visit_statement_before_primary_effect(
523 &mut self,
524 state: &Self::FlowState,
525 _statement: &'mir mir::Statement<'tcx>,
526 _location: Location,
527 ) {
528 if let Some(before) = self.before.as_mut() {
529 before.push(diff_pretty(state, &self.prev_state, self.analysis));
530 self.prev_state.clone_from(state)
531 }
dfeec247 532 }
dfeec247 533
1b1a35ee
XL
534 fn visit_statement_after_primary_effect(
535 &mut self,
536 state: &Self::FlowState,
537 _statement: &'mir mir::Statement<'tcx>,
538 _location: Location,
539 ) {
540 self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
541 self.prev_state.clone_from(state)
dfeec247
XL
542 }
543
1b1a35ee 544 fn visit_terminator_before_primary_effect(
dfeec247 545 &mut self,
1b1a35ee
XL
546 state: &Self::FlowState,
547 _terminator: &'mir mir::Terminator<'tcx>,
548 _location: Location,
549 ) {
550 if let Some(before) = self.before.as_mut() {
551 before.push(diff_pretty(state, &self.prev_state, self.analysis));
552 self.prev_state.clone_from(state)
e74abb32 553 }
dfeec247 554 }
dfeec247 555
1b1a35ee
XL
556 fn visit_terminator_after_primary_effect(
557 &mut self,
558 state: &Self::FlowState,
559 _terminator: &'mir mir::Terminator<'tcx>,
560 _location: Location,
561 ) {
562 self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
563 self.prev_state.clone_from(state)
564 }
dfeec247
XL
565}
566
1b1a35ee
XL
567macro_rules! regex {
568 ($re:literal $(,)?) => {{
569 static RE: SyncOnceCell<regex::Regex> = SyncOnceCell::new();
570 RE.get_or_init(|| Regex::new($re).unwrap())
571 }};
dfeec247
XL
572}
573
1b1a35ee 574fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
dfeec247 575where
1b1a35ee 576 T: DebugWithContext<C>,
dfeec247 577{
1b1a35ee
XL
578 if new == old {
579 return String::new();
dfeec247
XL
580 }
581
1b1a35ee 582 let re = regex!("\t?\u{001f}([+-])");
dfeec247 583
1b1a35ee 584 let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
dfeec247 585
1b1a35ee
XL
586 // Replace newlines in the `Debug` output with `<br/>`
587 let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
dfeec247 588
1b1a35ee
XL
589 let mut inside_font_tag = false;
590 let html_diff = re.replace_all(&raw_diff, |captures: &regex::Captures<'_>| {
591 let mut ret = String::new();
592 if inside_font_tag {
593 ret.push_str(r#"</font>"#);
e74abb32
XL
594 }
595
1b1a35ee
XL
596 let tag = match &captures[1] {
597 "+" => r#"<font color="darkgreen">+"#,
598 "-" => r#"<font color="red">-"#,
599 _ => unreachable!(),
dfeec247 600 };
e74abb32 601
1b1a35ee
XL
602 inside_font_tag = true;
603 ret.push_str(tag);
604 ret
605 });
74b04a01 606
1b1a35ee
XL
607 let mut html_diff = match html_diff {
608 Cow::Borrowed(_) => return raw_diff,
609 Cow::Owned(s) => s,
610 };
e74abb32 611
1b1a35ee
XL
612 if inside_font_tag {
613 html_diff.push_str("</font>");
e74abb32
XL
614 }
615
1b1a35ee 616 html_diff
e74abb32
XL
617}
618
619/// The background color used for zebra-striping the table.
620#[derive(Clone, Copy)]
621enum Background {
622 Light,
623 Dark,
624}
625
626impl Background {
627 fn attr(self) -> &'static str {
628 match self {
629 Self::Dark => "bgcolor=\"#f0f0f0\"",
630 Self::Light => "",
631 }
632 }
633}
634
635impl ops::Not for Background {
636 type Output = Self;
637
638 fn not(self) -> Self {
639 match self {
640 Self::Light => Self::Dark,
641 Self::Dark => Self::Light,
642 }
643 }
644}