1use std::borrow::Cow;
4use std::cell::RefCell;
5use std::ffi::OsString;
6use std::path::PathBuf;
7use std::sync::OnceLock;
8use std::{io, ops, str};
9
10use regex::Regex;
11use rustc_hir::def_id::DefId;
12use rustc_index::bit_set::DenseBitSet;
13use rustc_middle::mir::{
14 self, BasicBlock, Body, Location, create_dump_file, dump_enabled, graphviz_safe_def_name,
15 traversal,
16};
17use rustc_middle::ty::TyCtxt;
18use rustc_middle::ty::print::with_no_trimmed_paths;
19use rustc_span::{Symbol, sym};
20use tracing::debug;
21use {rustc_ast as ast, rustc_graphviz as dot};
22
23use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
24use super::{
25 Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor, visit_results,
26};
27use crate::errors::{
28 DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter,
29};
30
31pub(super) fn write_graphviz_results<'tcx, A>(
35 tcx: TyCtxt<'tcx>,
36 body: &Body<'tcx>,
37 analysis: &mut A,
38 results: &Results<A::Domain>,
39 pass_name: Option<&'static str>,
40) -> std::io::Result<()>
41where
42 A: Analysis<'tcx>,
43 A::Domain: DebugWithContext<A>,
44{
45 use std::fs;
46 use std::io::Write;
47
48 let def_id = body.source.def_id();
49 let Ok(attrs) = RustcMirAttrs::parse(tcx, def_id) else {
50 return Ok(());
52 };
53
54 let file = try {
55 match attrs.output_path(A::NAME) {
56 Some(path) => {
57 debug!("printing dataflow results for {:?} to {}", def_id, path.display());
58 if let Some(parent) = path.parent() {
59 fs::create_dir_all(parent)?;
60 }
61 fs::File::create_buffered(&path)?
62 }
63
64 None if dump_enabled(tcx, A::NAME, def_id) => {
65 create_dump_file(tcx, "dot", false, A::NAME, &pass_name.unwrap_or("-----"), body)?
66 }
67
68 _ => return Ok(()),
69 }
70 };
71 let mut file = match file {
72 Ok(f) => f,
73 Err(e) => return Err(e),
74 };
75
76 let style = match attrs.formatter {
77 Some(sym::two_phase) => OutputStyle::BeforeAndAfter,
78 _ => OutputStyle::AfterOnly,
79 };
80
81 let mut buf = Vec::new();
82
83 let graphviz = Formatter::new(body, analysis, results, style);
84 let mut render_opts =
85 vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
86 if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
87 render_opts.push(dot::RenderOption::DarkTheme);
88 }
89 let r = with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts));
90
91 let lhs = try {
92 r?;
93 file.write_all(&buf)?;
94 };
95
96 lhs
97}
98
99#[derive(Default)]
100struct RustcMirAttrs {
101 basename_and_suffix: Option<PathBuf>,
102 formatter: Option<Symbol>,
103}
104
105impl RustcMirAttrs {
106 fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result<Self, ()> {
107 let mut result = Ok(());
108 let mut ret = RustcMirAttrs::default();
109
110 let rustc_mir_attrs = tcx
111 .get_attrs(def_id, sym::rustc_mir)
112 .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
113
114 for attr in rustc_mir_attrs {
115 let attr_result = match attr.name() {
116 Some(name @ sym::borrowck_graphviz_postflow) => {
117 Self::set_field(&mut ret.basename_and_suffix, tcx, name, &attr, |s| {
118 let path = PathBuf::from(s.to_string());
119 match path.file_name() {
120 Some(_) => Ok(path),
121 None => {
122 tcx.dcx().emit_err(PathMustEndInFilename { span: attr.span() });
123 Err(())
124 }
125 }
126 })
127 }
128 Some(name @ sym::borrowck_graphviz_format) => {
129 Self::set_field(&mut ret.formatter, tcx, name, &attr, |s| match s {
130 sym::two_phase => Ok(s),
131 _ => {
132 tcx.dcx().emit_err(UnknownFormatter { span: attr.span() });
133 Err(())
134 }
135 })
136 }
137 _ => Ok(()),
138 };
139
140 result = result.and(attr_result);
141 }
142
143 result.map(|()| ret)
144 }
145
146 fn set_field<T>(
147 field: &mut Option<T>,
148 tcx: TyCtxt<'_>,
149 name: Symbol,
150 attr: &ast::MetaItemInner,
151 mapper: impl FnOnce(Symbol) -> Result<T, ()>,
152 ) -> Result<(), ()> {
153 if field.is_some() {
154 tcx.dcx().emit_err(DuplicateValuesFor { span: attr.span(), name });
155
156 return Err(());
157 }
158
159 if let Some(s) = attr.value_str() {
160 *field = Some(mapper(s)?);
161 Ok(())
162 } else {
163 tcx.dcx()
164 .emit_err(RequiresAnArgument { span: attr.span(), name: attr.name().unwrap() });
165 Err(())
166 }
167 }
168
169 fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
176 let mut ret = self.basename_and_suffix.as_ref().cloned()?;
177 let suffix = ret.file_name().unwrap(); let mut file_name: OsString = analysis_name.into();
180 file_name.push("_");
181 file_name.push(suffix);
182 ret.set_file_name(file_name);
183
184 Some(ret)
185 }
186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189enum OutputStyle {
190 AfterOnly,
191 BeforeAndAfter,
192}
193
194impl OutputStyle {
195 fn num_state_columns(&self) -> usize {
196 match self {
197 Self::AfterOnly => 1,
198 Self::BeforeAndAfter => 2,
199 }
200 }
201}
202
203struct Formatter<'mir, 'tcx, A>
204where
205 A: Analysis<'tcx>,
206{
207 body: &'mir Body<'tcx>,
208 analysis: RefCell<&'mir mut A>,
213 results: &'mir Results<A::Domain>,
214 style: OutputStyle,
215 reachable: DenseBitSet<BasicBlock>,
216}
217
218impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A>
219where
220 A: Analysis<'tcx>,
221{
222 fn new(
223 body: &'mir Body<'tcx>,
224 analysis: &'mir mut A,
225 results: &'mir Results<A::Domain>,
226 style: OutputStyle,
227 ) -> Self {
228 let reachable = traversal::reachable_as_bitset(body);
229 Formatter { body, analysis: analysis.into(), results, style, reachable }
230 }
231}
232
233#[derive(Copy, Clone, PartialEq, Eq, Debug)]
235struct CfgEdge {
236 source: BasicBlock,
237 index: usize,
238}
239
240fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
241 body[bb]
242 .terminator()
243 .successors()
244 .enumerate()
245 .map(|(index, _)| CfgEdge { source: bb, index })
246 .collect()
247}
248
249impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A>
250where
251 A: Analysis<'tcx>,
252 A::Domain: DebugWithContext<A>,
253{
254 type Node = BasicBlock;
255 type Edge = CfgEdge;
256
257 fn graph_id(&self) -> dot::Id<'_> {
258 let name = graphviz_safe_def_name(self.body.source.def_id());
259 dot::Id::new(format!("graph_for_def_id_{name}")).unwrap()
260 }
261
262 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
263 dot::Id::new(format!("bb_{}", n.index())).unwrap()
264 }
265
266 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
267 let analysis = &mut **self.analysis.borrow_mut();
268
269 let diffs = StateDiffCollector::run(self.body, *block, analysis, self.results, self.style);
270
271 let mut fmt = BlockFormatter {
272 cursor: ResultsCursor::new_borrowing(self.body, analysis, self.results),
273 style: self.style,
274 bg: Background::Light,
275 };
276 let label = fmt.write_node_label(*block, diffs).unwrap();
277
278 dot::LabelText::html(String::from_utf8(label).unwrap())
279 }
280
281 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
282 Some(dot::LabelText::label("none"))
283 }
284
285 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
286 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
287 dot::LabelText::label(label.clone())
288 }
289}
290
291impl<'tcx, A> dot::GraphWalk<'_> for Formatter<'_, 'tcx, A>
292where
293 A: Analysis<'tcx>,
294{
295 type Node = BasicBlock;
296 type Edge = CfgEdge;
297
298 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
299 self.body
300 .basic_blocks
301 .indices()
302 .filter(|&idx| self.reachable.contains(idx))
303 .collect::<Vec<_>>()
304 .into()
305 }
306
307 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
308 self.body
309 .basic_blocks
310 .indices()
311 .flat_map(|bb| dataflow_successors(self.body, bb))
312 .collect::<Vec<_>>()
313 .into()
314 }
315
316 fn source(&self, edge: &Self::Edge) -> Self::Node {
317 edge.source
318 }
319
320 fn target(&self, edge: &Self::Edge) -> Self::Node {
321 self.body[edge.source].terminator().successors().nth(edge.index).unwrap()
322 }
323}
324
325struct BlockFormatter<'mir, 'tcx, A>
326where
327 A: Analysis<'tcx>,
328{
329 cursor: ResultsCursor<'mir, 'tcx, A>,
330 bg: Background,
331 style: OutputStyle,
332}
333
334impl<'tcx, A> BlockFormatter<'_, 'tcx, A>
335where
336 A: Analysis<'tcx>,
337 A::Domain: DebugWithContext<A>,
338{
339 const HEADER_COLOR: &'static str = "#a0a0a0";
340
341 fn toggle_background(&mut self) -> Background {
342 let bg = self.bg;
343 self.bg = !bg;
344 bg
345 }
346
347 fn write_node_label(
348 &mut self,
349 block: BasicBlock,
350 diffs: StateDiffCollector<A::Domain>,
351 ) -> io::Result<Vec<u8>> {
352 use std::io::Write;
353
354 let mut v = vec![];
381 let w = &mut v;
382
383 let table_fmt = concat!(
384 " border=\"1\"",
385 " cellborder=\"1\"",
386 " cellspacing=\"0\"",
387 " cellpadding=\"3\"",
388 " sides=\"rb\"",
389 );
390 write!(w, r#"<table{table_fmt}>"#)?;
391
392 match self.style {
394 OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
395 OutputStyle::BeforeAndAfter => {
396 self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
397 }
398 }
399
400 self.bg = Background::Light;
402 self.cursor.seek_to_block_start(block);
403 let block_start_state = self.cursor.get().clone();
404 self.write_row_with_full_state(w, "", "(on start)")?;
405
406 self.write_statements_and_terminator(w, block, diffs)?;
408
409 let terminator = self.cursor.body()[block].terminator();
412
413 self.cursor.seek_to_block_end(block);
416 if self.cursor.get() != &block_start_state || A::Direction::IS_BACKWARD {
417 let after_terminator_name = match terminator.kind {
418 mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)",
419 _ => "(on end)",
420 };
421
422 self.write_row_with_full_state(w, "", after_terminator_name)?;
423 }
424
425 match terminator.kind {
431 mir::TerminatorKind::Call { destination, .. } => {
432 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
433 let state_on_unwind = this.cursor.get().clone();
434 this.cursor.apply_custom_effect(|analysis, state| {
435 analysis.apply_call_return_effect(
436 state,
437 block,
438 CallReturnPlaces::Call(destination),
439 );
440 });
441
442 write!(
443 w,
444 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
445 colspan = this.style.num_state_columns(),
446 fmt = fmt,
447 diff = diff_pretty(
448 this.cursor.get(),
449 &state_on_unwind,
450 this.cursor.analysis()
451 ),
452 )
453 })?;
454 }
455
456 mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
457 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
458 let state_on_coroutine_drop = this.cursor.get().clone();
459 this.cursor.apply_custom_effect(|analysis, state| {
460 analysis.apply_call_return_effect(
461 state,
462 resume,
463 CallReturnPlaces::Yield(resume_arg),
464 );
465 });
466
467 write!(
468 w,
469 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
470 colspan = this.style.num_state_columns(),
471 fmt = fmt,
472 diff = diff_pretty(
473 this.cursor.get(),
474 &state_on_coroutine_drop,
475 this.cursor.analysis()
476 ),
477 )
478 })?;
479 }
480
481 mir::TerminatorKind::InlineAsm { ref targets, ref operands, .. }
482 if !targets.is_empty() =>
483 {
484 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
485 let state_on_unwind = this.cursor.get().clone();
486 this.cursor.apply_custom_effect(|analysis, state| {
487 analysis.apply_call_return_effect(
488 state,
489 block,
490 CallReturnPlaces::InlineAsm(operands),
491 );
492 });
493
494 write!(
495 w,
496 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
497 colspan = this.style.num_state_columns(),
498 fmt = fmt,
499 diff = diff_pretty(
500 this.cursor.get(),
501 &state_on_unwind,
502 this.cursor.analysis()
503 ),
504 )
505 })?;
506 }
507
508 _ => {}
509 };
510
511 write!(w, "</table>")?;
512
513 Ok(v)
514 }
515
516 fn write_block_header_simple(
517 &mut self,
518 w: &mut impl io::Write,
519 block: BasicBlock,
520 ) -> io::Result<()> {
521 write!(
530 w,
531 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
532 block_id = block.index(),
533 )?;
534
535 write!(
537 w,
538 concat!(
539 "<tr>",
540 r#"<td colspan="2" {fmt}>MIR</td>"#,
541 r#"<td {fmt}>STATE</td>"#,
542 "</tr>",
543 ),
544 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
545 )
546 }
547
548 fn write_block_header_with_state_columns(
549 &mut self,
550 w: &mut impl io::Write,
551 block: BasicBlock,
552 state_column_names: &[&str],
553 ) -> io::Result<()> {
554 write!(
563 w,
564 concat!(
565 "<tr>",
566 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
567 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
568 "</tr>",
569 ),
570 fmt = "sides=\"tl\"",
571 num_state_cols = state_column_names.len(),
572 block_id = block.index(),
573 )?;
574
575 let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
577 write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
578
579 for name in state_column_names {
580 write!(w, "<td {fmt}>{name}</td>")?;
581 }
582
583 write!(w, "</tr>")
584 }
585
586 fn write_statements_and_terminator(
587 &mut self,
588 w: &mut impl io::Write,
589 block: BasicBlock,
590 diffs: StateDiffCollector<A::Domain>,
591 ) -> io::Result<()> {
592 let mut diffs_before = diffs.before.map(|v| v.into_iter());
593 let mut diffs_after = diffs.after.into_iter();
594
595 let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
596 if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() }
597 };
598
599 for (i, statement) in self.cursor.body()[block].statements.iter().enumerate() {
600 let statement_str = format!("{statement:?}");
601 let index_str = format!("{i}");
602
603 let after = next_in_dataflow_order(&mut diffs_after);
604 let before = diffs_before.as_mut().map(next_in_dataflow_order);
605
606 self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
607 if let Some(before) = before {
608 write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
609 }
610
611 write!(w, r#"<td {fmt} align="left">{after}</td>"#)
612 })?;
613 }
614
615 let after = next_in_dataflow_order(&mut diffs_after);
616 let before = diffs_before.as_mut().map(next_in_dataflow_order);
617
618 assert!(diffs_after.is_empty());
619 assert!(diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty));
620
621 let terminator = self.cursor.body()[block].terminator();
622 let mut terminator_str = String::new();
623 terminator.kind.fmt_head(&mut terminator_str).unwrap();
624
625 self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
626 if let Some(before) = before {
627 write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
628 }
629
630 write!(w, r#"<td {fmt} align="left">{after}</td>"#)
631 })
632 }
633
634 fn write_row<W: io::Write>(
637 &mut self,
638 w: &mut W,
639 i: &str,
640 mir: &str,
641 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
642 ) -> io::Result<()> {
643 let bg = self.toggle_background();
644 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
645
646 let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
647
648 write!(
649 w,
650 concat!(
651 "<tr>",
652 r#"<td {fmt} align="right">{i}</td>"#,
653 r#"<td {fmt} align="left">{mir}</td>"#,
654 ),
655 i = i,
656 fmt = fmt,
657 mir = dot::escape_html(mir),
658 )?;
659
660 f(self, w, &fmt)?;
661 write!(w, "</tr>")
662 }
663
664 fn write_row_with_full_state(
665 &mut self,
666 w: &mut impl io::Write,
667 i: &str,
668 mir: &str,
669 ) -> io::Result<()> {
670 self.write_row(w, i, mir, |this, w, fmt| {
671 let state = this.cursor.get();
672 let analysis = this.cursor.analysis();
673
674 write!(
677 w,
678 r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
679 colspan = this.style.num_state_columns(),
680 fmt = fmt,
681 state = dot::escape_html(&format!(
682 "{:?}",
683 DebugWithAdapter { this: state, ctxt: analysis }
684 )),
685 )
686 })
687 }
688}
689
690struct StateDiffCollector<D> {
691 prev_state: D,
692 before: Option<Vec<String>>,
693 after: Vec<String>,
694}
695
696impl<D> StateDiffCollector<D> {
697 fn run<'tcx, A>(
698 body: &Body<'tcx>,
699 block: BasicBlock,
700 analysis: &mut A,
701 results: &Results<A::Domain>,
702 style: OutputStyle,
703 ) -> Self
704 where
705 A: Analysis<'tcx, Domain = D>,
706 D: DebugWithContext<A>,
707 {
708 let mut collector = StateDiffCollector {
709 prev_state: analysis.bottom_value(body),
710 after: vec![],
711 before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
712 };
713
714 visit_results(body, std::iter::once(block), analysis, results, &mut collector);
715 collector
716 }
717}
718
719impl<'tcx, A> ResultsVisitor<'tcx, A> for StateDiffCollector<A::Domain>
720where
721 A: Analysis<'tcx>,
722 A::Domain: DebugWithContext<A>,
723{
724 fn visit_block_start(&mut self, state: &A::Domain) {
725 if A::Direction::IS_FORWARD {
726 self.prev_state.clone_from(state);
727 }
728 }
729
730 fn visit_block_end(&mut self, state: &A::Domain) {
731 if A::Direction::IS_BACKWARD {
732 self.prev_state.clone_from(state);
733 }
734 }
735
736 fn visit_after_early_statement_effect(
737 &mut self,
738 analysis: &mut A,
739 state: &A::Domain,
740 _statement: &mir::Statement<'tcx>,
741 _location: Location,
742 ) {
743 if let Some(before) = self.before.as_mut() {
744 before.push(diff_pretty(state, &self.prev_state, analysis));
745 self.prev_state.clone_from(state)
746 }
747 }
748
749 fn visit_after_primary_statement_effect(
750 &mut self,
751 analysis: &mut A,
752 state: &A::Domain,
753 _statement: &mir::Statement<'tcx>,
754 _location: Location,
755 ) {
756 self.after.push(diff_pretty(state, &self.prev_state, analysis));
757 self.prev_state.clone_from(state)
758 }
759
760 fn visit_after_early_terminator_effect(
761 &mut self,
762 analysis: &mut A,
763 state: &A::Domain,
764 _terminator: &mir::Terminator<'tcx>,
765 _location: Location,
766 ) {
767 if let Some(before) = self.before.as_mut() {
768 before.push(diff_pretty(state, &self.prev_state, analysis));
769 self.prev_state.clone_from(state)
770 }
771 }
772
773 fn visit_after_primary_terminator_effect(
774 &mut self,
775 analysis: &mut A,
776 state: &A::Domain,
777 _terminator: &mir::Terminator<'tcx>,
778 _location: Location,
779 ) {
780 self.after.push(diff_pretty(state, &self.prev_state, analysis));
781 self.prev_state.clone_from(state)
782 }
783}
784
785macro_rules! regex {
786 ($re:literal $(,)?) => {{
787 static RE: OnceLock<regex::Regex> = OnceLock::new();
788 RE.get_or_init(|| Regex::new($re).unwrap())
789 }};
790}
791
792fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
793where
794 T: DebugWithContext<C>,
795{
796 if new == old {
797 return String::new();
798 }
799
800 let re = regex!("\t?\u{001f}([+-])");
801
802 let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
803
804 let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
806
807 let mut inside_font_tag = false;
808 let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
809 let mut ret = String::new();
810 if inside_font_tag {
811 ret.push_str(r#"</font>"#);
812 }
813
814 let tag = match &captures[1] {
815 "+" => r#"<font color="darkgreen">+"#,
816 "-" => r#"<font color="red">-"#,
817 _ => unreachable!(),
818 };
819
820 inside_font_tag = true;
821 ret.push_str(tag);
822 ret
823 });
824
825 let Cow::Owned(mut html_diff) = html_diff else {
826 return raw_diff;
827 };
828
829 if inside_font_tag {
830 html_diff.push_str("</font>");
831 }
832
833 html_diff
834}
835
836#[derive(Clone, Copy)]
838enum Background {
839 Light,
840 Dark,
841}
842
843impl Background {
844 fn attr(self) -> &'static str {
845 match self {
846 Self::Dark => "bgcolor=\"#f0f0f0\"",
847 Self::Light => "",
848 }
849 }
850}
851
852impl ops::Not for Background {
853 type Output = Self;
854
855 fn not(self) -> Self {
856 match self {
857 Self::Light => Self::Dark,
858 Self::Dark => Self::Light,
859 }
860 }
861}