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