]> git.proxmox.com Git - rustc.git/blob - src/librustc_mir/dataflow/framework/graphviz.rs
New upstream version 1.45.0+dfsg1
[rustc.git] / src / librustc_mir / dataflow / framework / graphviz.rs
1 //! A helpful diagram for debugging dataflow problems.
2
3 use std::cell::RefCell;
4 use std::{io, ops, str};
5
6 use rustc_hir::def_id::DefId;
7 use rustc_index::bit_set::{BitSet, HybridBitSet};
8 use rustc_index::vec::{Idx, IndexVec};
9 use rustc_middle::mir::{self, BasicBlock, Body, Location};
10
11 use super::{Analysis, Direction, GenKillSet, Results, ResultsRefCursor};
12 use crate::util::graphviz_safe_def_name;
13
14 pub struct Formatter<'a, 'tcx, A>
15 where
16 A: Analysis<'tcx>,
17 {
18 body: &'a Body<'tcx>,
19 def_id: DefId,
20
21 // This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
22 block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
23 }
24
25 impl<A> Formatter<'a, 'tcx, A>
26 where
27 A: Analysis<'tcx>,
28 {
29 pub fn new(
30 body: &'a Body<'tcx>,
31 def_id: DefId,
32 results: &'a Results<'tcx, A>,
33 state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
34 ) -> Self {
35 let block_formatter = BlockFormatter {
36 bg: Background::Light,
37 results: ResultsRefCursor::new(body, results),
38 state_formatter,
39 };
40
41 Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
42 }
43 }
44
45 /// A pair of a basic block and an index into that basic blocks `successors`.
46 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
47 pub struct CfgEdge {
48 source: BasicBlock,
49 index: usize,
50 }
51
52 fn dataflow_successors(body: &Body<'tcx>, bb: BasicBlock) -> Vec<CfgEdge> {
53 body[bb]
54 .terminator()
55 .successors()
56 .enumerate()
57 .map(|(index, _)| CfgEdge { source: bb, index })
58 .collect()
59 }
60
61 impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
62 where
63 A: Analysis<'tcx>,
64 {
65 type Node = BasicBlock;
66 type Edge = CfgEdge;
67
68 fn graph_id(&self) -> dot::Id<'_> {
69 let name = graphviz_safe_def_name(self.def_id);
70 dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
71 }
72
73 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
74 dot::Id::new(format!("bb_{}", n.index())).unwrap()
75 }
76
77 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
78 let mut label = Vec::new();
79 self.block_formatter.borrow_mut().write_node_label(&mut label, self.body, *block).unwrap();
80 dot::LabelText::html(String::from_utf8(label).unwrap())
81 }
82
83 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
84 Some(dot::LabelText::label("none"))
85 }
86
87 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
88 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
89 dot::LabelText::label(label.clone())
90 }
91 }
92
93 impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
94 where
95 A: Analysis<'tcx>,
96 {
97 type Node = BasicBlock;
98 type Edge = CfgEdge;
99
100 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
101 self.body.basic_blocks().indices().collect::<Vec<_>>().into()
102 }
103
104 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
105 self.body
106 .basic_blocks()
107 .indices()
108 .flat_map(|bb| dataflow_successors(self.body, bb))
109 .collect::<Vec<_>>()
110 .into()
111 }
112
113 fn source(&self, edge: &Self::Edge) -> Self::Node {
114 edge.source
115 }
116
117 fn target(&self, edge: &Self::Edge) -> Self::Node {
118 self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap()
119 }
120 }
121
122 struct BlockFormatter<'a, 'tcx, A>
123 where
124 A: Analysis<'tcx>,
125 {
126 results: ResultsRefCursor<'a, 'a, 'tcx, A>,
127 bg: Background,
128 state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
129 }
130
131 impl<A> BlockFormatter<'a, 'tcx, A>
132 where
133 A: Analysis<'tcx>,
134 {
135 const HEADER_COLOR: &'static str = "#a0a0a0";
136
137 fn num_state_columns(&self) -> usize {
138 std::cmp::max(1, self.state_formatter.column_names().len())
139 }
140
141 fn toggle_background(&mut self) -> Background {
142 let bg = self.bg;
143 self.bg = !bg;
144 bg
145 }
146
147 fn write_node_label(
148 &mut self,
149 w: &mut impl io::Write,
150 body: &'a Body<'tcx>,
151 block: BasicBlock,
152 ) -> io::Result<()> {
153 // Sample output:
154 // +-+-----------------------------------------------+
155 // A | bb4 |
156 // +-+----------------------------------+------------+
157 // B | MIR | STATE |
158 // +-+----------------------------------+------------+
159 // C | | (on entry) | {_0,_2,_3} |
160 // +-+----------------------------------+------------+
161 // D |0| StorageLive(_7) | |
162 // +-+----------------------------------+------------+
163 // |1| StorageLive(_8) | |
164 // +-+----------------------------------+------------+
165 // |2| _8 = &mut _1 | +_8 |
166 // +-+----------------------------------+------------+
167 // E |T| _4 = const Foo::twiddle(move _2) | -_2 |
168 // +-+----------------------------------+------------+
169 // F | | (on unwind) | {_0,_3,_8} |
170 // +-+----------------------------------+------------+
171 // | | (on successful return) | +_4 |
172 // +-+----------------------------------+------------+
173
174 // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
175 // children. This is because `xdot` seemed to have a hard time correctly propagating
176 // attributes. Make sure to test the output before trying to remove the redundancy.
177 // Notably, `align` was found to have no effect when applied only to <table>.
178
179 let table_fmt = concat!(
180 " border=\"1\"",
181 " cellborder=\"1\"",
182 " cellspacing=\"0\"",
183 " cellpadding=\"3\"",
184 " sides=\"rb\"",
185 );
186 write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
187
188 // A + B: Block header
189 if self.state_formatter.column_names().is_empty() {
190 self.write_block_header_simple(w, block)?;
191 } else {
192 self.write_block_header_with_state_columns(w, block)?;
193 }
194
195 // C: State at start of block
196 self.bg = Background::Light;
197 self.results.seek_to_block_start(block);
198 let block_entry_state = self.results.get().clone();
199
200 self.write_row_with_full_state(w, "", "(on start)")?;
201
202 // D: Statement transfer functions
203 for (i, statement) in body[block].statements.iter().enumerate() {
204 let location = Location { block, statement_index: i };
205 let statement_str = format!("{:?}", statement);
206 self.write_row_for_location(w, &i.to_string(), &statement_str, location)?;
207 }
208
209 // E: Terminator transfer function
210 let terminator = body[block].terminator();
211 let terminator_loc = body.terminator_loc(block);
212 let mut terminator_str = String::new();
213 terminator.kind.fmt_head(&mut terminator_str).unwrap();
214
215 self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?;
216
217 // F: State at end of block
218
219 // Write the full dataflow state immediately after the terminator if it differs from the
220 // state at block entry.
221 self.results.seek_to_block_end(block);
222 if self.results.get() != &block_entry_state || A::Direction::is_backward() {
223 let after_terminator_name = match terminator.kind {
224 mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
225 _ => "(on end)",
226 };
227
228 self.write_row_with_full_state(w, "", after_terminator_name)?;
229 }
230
231 // Write any changes caused by terminator-specific effects
232 let num_state_columns = self.num_state_columns();
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| {
241 write!(
242 w,
243 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
244 colspan = num_state_columns,
245 fmt = fmt,
246 )?;
247
248 let state_on_unwind = this.results.get().clone();
249 this.results.apply_custom_effect(|analysis, state| {
250 analysis.apply_call_return_effect(state, block, func, args, return_place);
251 });
252
253 write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
254 write!(w, "</td>")
255 })?;
256 }
257
258 mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
259 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
260 write!(
261 w,
262 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
263 colspan = num_state_columns,
264 fmt = fmt,
265 )?;
266
267 let state_on_generator_drop = this.results.get().clone();
268 this.results.apply_custom_effect(|analysis, state| {
269 analysis.apply_yield_resume_effect(state, resume, resume_arg);
270 });
271
272 write_diff(
273 w,
274 this.results.analysis(),
275 &state_on_generator_drop,
276 this.results.get(),
277 )?;
278 write!(w, "</td>")
279 })?;
280 }
281
282 _ => {}
283 };
284
285 write!(w, "</table>")
286 }
287
288 fn write_block_header_simple(
289 &mut self,
290 w: &mut impl io::Write,
291 block: BasicBlock,
292 ) -> io::Result<()> {
293 // +-------------------------------------------------+
294 // A | bb4 |
295 // +-----------------------------------+-------------+
296 // B | MIR | STATE |
297 // +-+---------------------------------+-------------+
298 // | | ... | |
299
300 // A
301 write!(
302 w,
303 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
304 block_id = block.index(),
305 )?;
306
307 // B
308 write!(
309 w,
310 concat!(
311 "<tr>",
312 r#"<td colspan="2" {fmt}>MIR</td>"#,
313 r#"<td {fmt}>STATE</td>"#,
314 "</tr>",
315 ),
316 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
317 )
318 }
319
320 fn write_block_header_with_state_columns(
321 &mut self,
322 w: &mut impl io::Write,
323 block: BasicBlock,
324 ) -> io::Result<()> {
325 // +------------------------------------+-------------+
326 // A | bb4 | STATE |
327 // +------------------------------------+------+------+
328 // B | MIR | GEN | KILL |
329 // +-+----------------------------------+------+------+
330 // | | ... | | |
331
332 let state_column_names = self.state_formatter.column_names();
333
334 // A
335 write!(
336 w,
337 concat!(
338 "<tr>",
339 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
340 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
341 "</tr>",
342 ),
343 fmt = "sides=\"tl\"",
344 num_state_cols = state_column_names.len(),
345 block_id = block.index(),
346 )?;
347
348 // B
349 let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
350 write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
351
352 for name in state_column_names {
353 write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
354 }
355
356 write!(w, "</tr>")
357 }
358
359 /// Write a row with the given index and MIR, using the function argument to fill in the
360 /// "STATE" column(s).
361 fn write_row<W: io::Write>(
362 &mut self,
363 w: &mut W,
364 i: &str,
365 mir: &str,
366 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
367 ) -> io::Result<()> {
368 let bg = self.toggle_background();
369 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
370
371 let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
372
373 write!(
374 w,
375 concat!(
376 "<tr>",
377 r#"<td {fmt} align="right">{i}</td>"#,
378 r#"<td {fmt} align="left">{mir}</td>"#,
379 ),
380 i = i,
381 fmt = fmt,
382 mir = dot::escape_html(mir),
383 )?;
384
385 f(self, w, &fmt)?;
386 write!(w, "</tr>")
387 }
388
389 fn write_row_with_full_state(
390 &mut self,
391 w: &mut impl io::Write,
392 i: &str,
393 mir: &str,
394 ) -> io::Result<()> {
395 self.write_row(w, i, mir, |this, w, fmt| {
396 let state = this.results.get();
397 let analysis = this.results.analysis();
398
399 write!(
400 w,
401 r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
402 colspan = this.num_state_columns(),
403 fmt = fmt,
404 )?;
405 pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
406 write!(w, "}}</td>")
407 })
408 }
409
410 fn write_row_for_location(
411 &mut self,
412 w: &mut impl io::Write,
413 i: &str,
414 mir: &str,
415 location: Location,
416 ) -> io::Result<()> {
417 self.write_row(w, i, mir, |this, w, fmt| {
418 this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
419 })
420 }
421 }
422
423 /// Controls what gets printed under the `STATE` header.
424 pub trait StateFormatter<'tcx, A>
425 where
426 A: Analysis<'tcx>,
427 {
428 /// The columns that will get printed under `STATE`.
429 fn column_names(&self) -> &[&str];
430
431 fn write_state_for_location(
432 &mut self,
433 w: &mut dyn io::Write,
434 fmt: &str,
435 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
436 location: Location,
437 ) -> io::Result<()>;
438 }
439
440 /// Prints a single column containing the state vector immediately *after* each statement.
441 pub struct SimpleDiff<'a, 'tcx, A>
442 where
443 A: Analysis<'tcx>,
444 {
445 prev_state: ResultsRefCursor<'a, 'a, 'tcx, A>,
446 }
447
448 impl<A> SimpleDiff<'a, 'tcx, A>
449 where
450 A: Analysis<'tcx>,
451 {
452 pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>) -> Self {
453 SimpleDiff { prev_state: ResultsRefCursor::new(body, results) }
454 }
455 }
456
457 impl<A> StateFormatter<'tcx, A> for SimpleDiff<'_, 'tcx, A>
458 where
459 A: Analysis<'tcx>,
460 {
461 fn column_names(&self) -> &[&str] {
462 &[]
463 }
464
465 fn write_state_for_location(
466 &mut self,
467 mut w: &mut dyn io::Write,
468 fmt: &str,
469 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
470 location: Location,
471 ) -> io::Result<()> {
472 if A::Direction::is_forward() {
473 if location.statement_index == 0 {
474 self.prev_state.seek_to_block_start(location.block);
475 } else {
476 self.prev_state.seek_after_primary_effect(Location {
477 statement_index: location.statement_index - 1,
478 ..location
479 });
480 }
481 } else {
482 if location == results.body().terminator_loc(location.block) {
483 self.prev_state.seek_to_block_end(location.block);
484 } else {
485 self.prev_state.seek_after_primary_effect(location.successor_within_block());
486 }
487 }
488
489 write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
490 results.seek_after_primary_effect(location);
491 let curr_state = results.get();
492 write_diff(&mut w, results.analysis(), self.prev_state.get(), curr_state)?;
493 write!(w, "</td>")
494 }
495 }
496
497 /// Prints two state columns, one containing only the "before" effect of each statement and one
498 /// containing the full effect.
499 pub struct TwoPhaseDiff<T: Idx> {
500 prev_state: BitSet<T>,
501 prev_loc: Location,
502 }
503
504 impl<T: Idx> TwoPhaseDiff<T> {
505 pub fn new(bits_per_block: usize) -> Self {
506 TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
507 }
508 }
509
510 impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
511 where
512 A: Analysis<'tcx>,
513 {
514 fn column_names(&self) -> &[&str] {
515 &["BEFORE", " AFTER"]
516 }
517
518 fn write_state_for_location(
519 &mut self,
520 mut w: &mut dyn io::Write,
521 fmt: &str,
522 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
523 location: Location,
524 ) -> io::Result<()> {
525 if location.statement_index == 0 {
526 results.seek_to_block_entry(location.block);
527 self.prev_state.overwrite(results.get());
528 } else {
529 // Ensure that we are visiting statements in order, so `prev_state` is correct.
530 assert_eq!(self.prev_loc.successor_within_block(), location);
531 }
532
533 self.prev_loc = location;
534
535 // Before
536
537 write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
538 results.seek_before_primary_effect(location);
539 let curr_state = results.get();
540 write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
541 self.prev_state.overwrite(curr_state);
542 write!(w, "</td>")?;
543
544 // After
545
546 write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
547 results.seek_after_primary_effect(location);
548 let curr_state = results.get();
549 write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
550 self.prev_state.overwrite(curr_state);
551 write!(w, "</td>")
552 }
553 }
554
555 /// Prints the gen/kill set for the entire block.
556 pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
557 body: &'a mir::Body<'tcx>,
558 trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
559 }
560
561 impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
562 pub fn new(
563 body: &'mir mir::Body<'tcx>,
564 trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
565 ) -> Self {
566 BlockTransferFunc { body, trans_for_block }
567 }
568 }
569
570 impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
571 where
572 A: Analysis<'tcx>,
573 {
574 fn column_names(&self) -> &[&str] {
575 &["GEN", "KILL"]
576 }
577
578 fn write_state_for_location(
579 &mut self,
580 mut w: &mut dyn io::Write,
581 fmt: &str,
582 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
583 location: Location,
584 ) -> io::Result<()> {
585 // Only print a single row.
586 if location.statement_index != 0 {
587 return Ok(());
588 }
589
590 let block_trans = &self.trans_for_block[location.block];
591 let rowspan = self.body.basic_blocks()[location.block].statements.len();
592
593 for set in &[&block_trans.gen, &block_trans.kill] {
594 write!(
595 w,
596 r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
597 fmt = fmt,
598 rowspan = rowspan
599 )?;
600
601 pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
602 write!(w, "</td>")?;
603 }
604
605 Ok(())
606 }
607 }
608
609 /// Writes two lines, one containing the added bits and one the removed bits.
610 fn write_diff<A: Analysis<'tcx>>(
611 w: &mut impl io::Write,
612 analysis: &A,
613 from: &BitSet<A::Idx>,
614 to: &BitSet<A::Idx>,
615 ) -> io::Result<()> {
616 assert_eq!(from.domain_size(), to.domain_size());
617 let len = from.domain_size();
618
619 let mut set = HybridBitSet::new_empty(len);
620 let mut clear = HybridBitSet::new_empty(len);
621
622 // FIXME: Implement a lazy iterator over the symmetric difference of two bitsets.
623 for i in (0..len).map(A::Idx::new) {
624 match (from.contains(i), to.contains(i)) {
625 (false, true) => set.insert(i),
626 (true, false) => clear.insert(i),
627 _ => continue,
628 };
629 }
630
631 if !set.is_empty() {
632 write!(w, r#"<font color="darkgreen">+"#)?;
633 pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
634 write!(w, r#"</font>"#)?;
635 }
636
637 if !set.is_empty() && !clear.is_empty() {
638 write!(w, "{}", BR_LEFT)?;
639 }
640
641 if !clear.is_empty() {
642 write!(w, r#"<font color="red">-"#)?;
643 pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
644 write!(w, r#"</font>"#)?;
645 }
646
647 Ok(())
648 }
649
650 const BR_LEFT: &str = r#"<br align="left"/>"#;
651 const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
652
653 /// Line break policy that breaks at 40 characters and starts the next line with a single space.
654 const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
655
656 struct LineBreak {
657 sequence: &'static str,
658 limit: usize,
659 }
660
661 /// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
662 /// separator (`sep`).
663 ///
664 /// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
665 /// character limit.
666 fn pretty_print_state_elems<A>(
667 w: &mut impl io::Write,
668 analysis: &A,
669 elems: impl Iterator<Item = A::Idx>,
670 sep: &str,
671 line_break: Option<LineBreak>,
672 ) -> io::Result<bool>
673 where
674 A: Analysis<'tcx>,
675 {
676 let sep_width = sep.chars().count();
677
678 let mut buf = Vec::new();
679
680 let mut first = true;
681 let mut curr_line_width = 0;
682 let mut line_break_inserted = false;
683
684 for idx in elems {
685 buf.clear();
686 analysis.pretty_print_idx(&mut buf, idx)?;
687 let idx_str =
688 str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
689 let escaped = dot::escape_html(idx_str);
690 let escaped_width = escaped.chars().count();
691
692 if first {
693 first = false;
694 } else {
695 write!(w, "{}", sep)?;
696 curr_line_width += sep_width;
697
698 if let Some(line_break) = &line_break {
699 if curr_line_width + sep_width + escaped_width > line_break.limit {
700 write!(w, "{}", line_break.sequence)?;
701 line_break_inserted = true;
702 curr_line_width = 0;
703 }
704 }
705 }
706
707 write!(w, "{}", escaped)?;
708 curr_line_width += escaped_width;
709 }
710
711 Ok(line_break_inserted)
712 }
713
714 /// The background color used for zebra-striping the table.
715 #[derive(Clone, Copy)]
716 enum Background {
717 Light,
718 Dark,
719 }
720
721 impl Background {
722 fn attr(self) -> &'static str {
723 match self {
724 Self::Dark => "bgcolor=\"#f0f0f0\"",
725 Self::Light => "",
726 }
727 }
728 }
729
730 impl ops::Not for Background {
731 type Output = Self;
732
733 fn not(self) -> Self {
734 match self {
735 Self::Light => Self::Dark,
736 Self::Dark => Self::Light,
737 }
738 }
739 }