Skip to main content

hegel/
pretty.rs

1//! Pretty-printing of generated values.
2//!
3//! [`Document`] owns one pretty-printed document: its builder methods
4//! choose the layout options, [`Document::printer`] exposes the surface to
5//! write through, and [`Document::finish`] consumes it to render exactly
6//! once at the end.
7//!
8//! [`PrettyPrinter`] is that write surface, wrapping libhegel's layout
9//! engine (an Oppen-style pretty-printer ported from Hypothesis's
10//! `hypothesis.vendor.pretty`). Output is built from three primitives:
11//! [`PrettyPrinter::text`] emits literal text, [`PrettyPrinter::breakable`]
12//! marks a point that renders as a separator when the enclosing group fits
13//! on one line and as a newline plus indentation when it does not, and
14//! [`PrettyPrinter::begin_group`] / [`PrettyPrinter::end_group`] delimit
15//! the groups those decisions are made over. A group either fits — every
16//! breakable renders as its separator — or breaks as a whole, outermost
17//! groups first.
18//!
19//! [`PrettyPrintable`] is the protocol a value uses to describe its own
20//! representation, in Rust-expression syntax wherever possible. It is
21//! implemented for the standard types the generator library produces,
22//! derivable for user types with `#[derive(PrettyPrintable)]`, and
23//! available for any `Debug` type — without writing an implementation —
24//! through [`pretty_print_as_debug!`](crate::pretty_print_as_debug).
25
26use crate::ffi::{PrinterCallError, PrinterHandle};
27use std::cell::Cell;
28use std::marker::PhantomData;
29
30/// Accept a printer operation's outcome: misuse panics with libhegel's
31/// diagnostic, while writing to a dead region — a straggling thread printing
32/// after the document was read, or into a region whose anchor was retracted
33/// — is a silent no-op, so a writer that outlives its document never brings
34/// the process down.
35fn tolerate(result: Result<(), PrinterCallError>) {
36    match result {
37        Ok(()) | Err(PrinterCallError::DeadRegion) => {}
38        Err(PrinterCallError::Other(message)) => panic!("{message}"),
39    }
40}
41use crate::test_case::invalid_argument;
42
43/// The line width documents are laid out to when none is configured.
44pub(crate) const DEFAULT_MAX_WIDTH: u64 = 79;
45
46/// One pretty-printed document: the owner of its layout options, its
47/// content, and its rendering.
48///
49/// Configure the layout with the builder methods (before anything is
50/// printed), write content through [`printer`](Document::printer), and
51/// render by consuming the document with [`finish`](Document::finish) —
52/// rendering happens exactly once, at the end. The [`PrettyPrinter`] this
53/// hands out is write-only, so code that is *given* a printer (a
54/// [`PrettyPrintable`] implementation, a
55/// [`PrintableGenerator`](crate::PrintableGenerator)) can never render or
56/// otherwise observe the document it is contributing to.
57///
58/// # Example
59///
60/// ```
61/// use hegel::Document;
62///
63/// let mut doc = Document::new().max_width(10);
64/// let p = doc.printer();
65/// p.begin_group(1, "[");
66/// p.text("first");
67/// p.text(",");
68/// p.breakable(" ");
69/// p.text("second");
70/// p.end_group("]");
71/// assert_eq!(doc.finish(), "[first,\n second]");
72/// ```
73#[derive(Debug)]
74pub struct Document {
75    max_width: u64,
76    printer: Option<PrettyPrinter>,
77}
78
79impl Document {
80    /// Create an empty document with the default layout options (a maximum
81    /// line width of 79 characters).
82    pub fn new() -> Self {
83        Document {
84            max_width: DEFAULT_MAX_WIDTH,
85            printer: None,
86        }
87    }
88
89    /// Keep lines within `max_width` characters where the group structure
90    /// allows it. Defaults to 79.
91    ///
92    /// Layout options describe the whole document, so they must be chosen
93    /// up front: calling this after [`printer`](Document::printer) has been
94    /// used is an error, as is a `max_width` of 0.
95    pub fn max_width(mut self, max_width: usize) -> Self {
96        if self.printer.is_some() {
97            invalid_argument!("max_width must be set before the document is printed to");
98        }
99        if max_width == 0 {
100            invalid_argument!("max_width must be positive");
101        }
102        self.max_width = max_width as u64;
103        self
104    }
105
106    /// The printer to write this document's content through.
107    pub fn printer(&mut self) -> &mut PrettyPrinter {
108        self.printer
109            .get_or_insert_with(|| PrettyPrinter::from_handle(PrinterHandle::new(self.max_width)))
110    }
111
112    /// Splice any outstanding deferred content into place, lay the document
113    /// out, and return it.
114    ///
115    /// Consuming the document is what makes rendering a once-at-the-end
116    /// operation; there is no way to observe a partially built document.
117    pub fn finish(mut self) -> String {
118        match &mut self.printer {
119            Some(printer) => printer.value(),
120            None => String::new(),
121        }
122    }
123}
124
125impl Default for Document {
126    fn default() -> Self {
127        Document::new()
128    }
129}
130
131/// The write surface of a pretty-printed document.
132///
133/// See the [module docs](self) for the printing model. Obtained from
134/// [`Document::printer`] — or received, already positioned, by printing
135/// code such as a [`PrettyPrintable`] implementation. Rejections of the
136/// layout protocol (an [`end_group`](PrettyPrinter::end_group) with no open
137/// group) panic, since they indicate a bug in the calling printing code.
138pub struct PrettyPrinter {
139    /// `None` is the no-op printer: every emitting method returns without
140    /// doing anything, so one drawing body can serve both the silent and the
141    /// printing draw paths.
142    handle: Option<PrinterHandle>,
143    /// A printer belongs to one thread at a time (it may move — the type is
144    /// `Send` — but never be shared), exactly like [`TestCase`]: the region
145    /// model makes cross-thread output deterministic only because each
146    /// region has a single writer.
147    ///
148    /// [`TestCase`]: crate::TestCase
149    _single_owner: PhantomData<Cell<()>>,
150}
151
152impl std::fmt::Debug for PrettyPrinter {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        f.debug_struct("PrettyPrinter")
155            .field("handle", &self.handle)
156            .finish()
157    }
158}
159
160impl PrettyPrinter {
161    /// Create a printer that discards everything printed to it.
162    ///
163    /// This is how a [`PrintableGenerator`](crate::PrintableGenerator) with
164    /// one shared drawing body implements its silent path:
165    /// [`Generator::do_draw`](crate::Generator::do_draw) simply calls
166    /// `self.do_draw_and_print(tc, &mut PrettyPrinter::noop())`. The
167    /// contract that both paths consume identical choices then holds by
168    /// construction. Guard any expensive formatting with
169    /// [`should_print`](PrettyPrinter::should_print) so the silent path
170    /// stays cheap.
171    pub fn noop() -> Self {
172        PrettyPrinter {
173            handle: None,
174            _single_owner: PhantomData,
175        }
176    }
177
178    /// Whether printing to this printer produces output: `false` for the
179    /// discarding printer returned by [`noop`](PrettyPrinter::noop). Use it
180    /// to skip work — formatting a value, say — whose only purpose is to be
181    /// printed.
182    pub fn should_print(&self) -> bool {
183        self.handle.is_some()
184    }
185
186    /// Wrap an existing engine printer handle (e.g. a test case's shared
187    /// document).
188    pub(crate) fn from_handle(handle: PrinterHandle) -> Self {
189        PrettyPrinter {
190            handle: Some(handle),
191            _single_owner: PhantomData,
192        }
193    }
194
195    /// Emit literal, unbreakable text.
196    ///
197    /// Newlines in `s` are honored as unconditional line breaks (equivalent
198    /// to [`hard_break`](PrettyPrinter::hard_break), so the new line starts
199    /// at the current indentation).
200    pub fn text(&mut self, s: &str) {
201        let Some(handle) = &self.handle else { return };
202        let mut first = true;
203        for segment in s.split('\n') {
204            if !first {
205                tolerate(handle.hard_break());
206            }
207            first = false;
208            if !segment.is_empty() {
209                tolerate(handle.text(segment));
210            }
211        }
212    }
213
214    /// Emit a potential break point: renders as `sep` if the enclosing group
215    /// fits on the current line, and as a newline plus the current
216    /// indentation if the group breaks.
217    pub fn breakable(&mut self, sep: &str) {
218        let Some(handle) = &self.handle else { return };
219        tolerate(handle.breakable(sep));
220    }
221
222    /// Emit an unconditional newline followed by the current indentation.
223    pub fn hard_break(&mut self) {
224        let Some(handle) = &self.handle else { return };
225        tolerate(handle.hard_break());
226    }
227
228    /// Open a group: emit `open`, then increase the indentation applied by
229    /// subsequent break points by `indent` (conventionally the width of
230    /// `open`, so continuation lines align just inside the delimiter).
231    pub fn begin_group(&mut self, indent: usize, open: &str) {
232        let Some(handle) = &self.handle else { return };
233        tolerate(handle.begin_group(indent as u64, open));
234    }
235
236    /// Close the innermost group: undo the indentation its
237    /// [`begin_group`](PrettyPrinter::begin_group) added, then emit `close`.
238    /// Panics if no group is open.
239    pub fn end_group(&mut self, close: &str) {
240        let Some(handle) = &self.handle else { return };
241        tolerate(handle.end_group(close));
242    }
243
244    /// Adjust the indentation applied by subsequent break points by `delta`.
245    pub fn shift_indent(&mut self, delta: isize) {
246        let Some(handle) = &self.handle else { return };
247        tolerate(handle.shift_indent(delta as i64));
248    }
249
250    /// Attach a comment to the line currently being written: `text` is
251    /// rendered as `  // text` at the end of that line, every group open at
252    /// this position is forced to break — nothing else may share a line with
253    /// a comment — and the comment is excluded from line-width accounting. A
254    /// group forced to break by a comment also breaks before its closing
255    /// delimiter, so the delimiter is not caught up in a comment on the
256    /// group's last element.
257    ///
258    /// `text` must not contain newlines; a comment is a single-line
259    /// construct.
260    pub fn comment(&mut self, text: &str) {
261        let Some(handle) = &self.handle else { return };
262        tolerate(handle.comment(&format!("  // {text}")));
263    }
264
265    /// Splice in any outstanding deferred content, flush pending break
266    /// points, and return everything printed so far. Only ever called by an
267    /// owner of the document — [`Document::finish`], or the run lifecycle
268    /// reading a test case's document — never by printing code, which only
269    /// sees the write surface. Panics on a layout error in the printed
270    /// content (an unbalanced `end_group` that could only be detected once
271    /// the whole document was assembled).
272    pub(crate) fn value(&mut self) -> String {
273        self.try_value()
274            .unwrap_or_else(|message| panic!("{message}"))
275    }
276
277    /// [`value`](PrettyPrinter::value), reporting a layout error in the
278    /// printed content as an `Err` instead of panicking — for the run
279    /// lifecycle, which renders the output of user printing code after the
280    /// test body's panic handling has finished and must not let a printing
281    /// bug take down the whole run.
282    pub(crate) fn try_value(&mut self) -> Result<String, String> {
283        let Some(handle) = &self.handle else {
284            unreachable!("only rendering printers have their value read");
285        };
286        let _ = handle.resolve();
287        match handle.value() {
288            Ok(rendered) => Ok(rendered),
289            Err(PrinterCallError::Other(message)) => Err(message),
290            Err(PrinterCallError::DeadRegion) => {
291                unreachable!("a document's own region never dies before it renders")
292            }
293        }
294    }
295
296    /// Open a speculative region: output printed through the returned
297    /// [`Speculation`] is held back until [`Speculation::commit`] emits it or
298    /// [`Speculation::abort`] discards it. Dropping the `Speculation` without
299    /// committing (e.g. on unwind) aborts it.
300    ///
301    /// This is how draw-time printing survives rejection: a combinator that
302    /// may retract a draw — a filter retry, a rejected collection element —
303    /// prints each attempt inside a speculative region and only commits the
304    /// accepted one.
305    pub fn speculate(&mut self) -> Speculation<'_> {
306        if let Some(handle) = &self.handle {
307            tolerate(handle.begin_speculative());
308        }
309        Speculation {
310            printer: self,
311            resolved: false,
312        }
313    }
314}
315
316/// Cloning a printer opens a *child region*: a hole in the document,
317/// anchored at the printer's current position, that the clone writes into.
318///
319/// Whatever the clone prints — at any later point, from any thread that owns
320/// it — appears at the anchor when the document renders, with line-breaking
321/// behaving as if it had been printed inline. This is how output crosses
322/// threads deterministically (each clone's output lands where the clone was
323/// made, however the threads were scheduled), and how a generator whose
324/// value's representation is only known during test execution (a
325/// Hegel-controlled random number generator, say) prints: it clones the
326/// printer at draw time and records into the clone as the value is used.
327///
328/// A child region dies when the document renders, or when a speculative
329/// region its anchor sat inside is aborted; a dead region's writes are
330/// silent no-ops, so a clone that outlives its document can keep trying to
331/// record without consequence. Cloning a no-op printer yields a no-op
332/// printer, and cloning into a dead region yields a printer whose writes
333/// discard.
334impl Clone for PrettyPrinter {
335    fn clone(&self) -> Self {
336        let handle = match &self.handle {
337            None => None,
338            Some(handle) => match handle.deferred() {
339                Ok(child) => Some(child),
340                Err(PrinterCallError::DeadRegion) => None,
341                Err(PrinterCallError::Other(message)) => unreachable!("{message}"),
342            },
343        };
344        PrettyPrinter {
345            handle,
346            _single_owner: PhantomData,
347        }
348    }
349}
350
351/// An open speculative region on a [`PrettyPrinter`]; see
352/// [`PrettyPrinter::speculate`].
353#[derive(Debug)]
354pub struct Speculation<'a> {
355    printer: &'a mut PrettyPrinter,
356    resolved: bool,
357}
358
359impl Speculation<'_> {
360    /// The printer to print the speculative output through.
361    pub fn printer(&mut self) -> &mut PrettyPrinter {
362        self.printer
363    }
364
365    /// Close the region, keeping its output.
366    pub fn commit(mut self) {
367        self.resolved = true;
368        if let Some(handle) = &self.printer.handle {
369            tolerate(handle.commit_speculative());
370        }
371    }
372
373    /// Close the region, discarding its output.
374    pub fn abort(mut self) {
375        self.resolved = true;
376        if let Some(handle) = &self.printer.handle {
377            tolerate(handle.abort_speculative());
378        }
379    }
380}
381
382/// Dropping an uncommitted speculation — most importantly during an unwind
383/// out of a speculative draw, such as a budget-exhausted `StopTest` or a
384/// failed assumption mid-attempt — discards its output, so a partial attempt
385/// never corrupts the document. The result is deliberately ignored: this can
386/// run during a panic, where a second panic would abort the process.
387impl Drop for Speculation<'_> {
388    fn drop(&mut self) {
389        if !self.resolved {
390            if let Some(handle) = &self.printer.handle {
391                let _ = handle.abort_speculative();
392            }
393        }
394    }
395}
396
397/// Print a `{:?}` representation through the layout machinery.
398///
399/// The output of a derived `Debug` implementation follows a small grammar —
400/// `Name { field: value, … }`, `Name(…)`, `(…)`, `[…]`, `{key: value, …}`,
401/// string and character literals, atoms — and this function re-emits it
402/// through the printer's group and breakable primitives, so a large value
403/// wraps exactly like one printed by `#[derive(PrettyPrintable)]`. Anything
404/// that doesn't parse as that grammar (a hand-written `Debug` can produce
405/// arbitrary text) is emitted verbatim, with embedded newlines honored as
406/// hard breaks.
407///
408/// This is the engine behind [`pretty_print_as_debug!`](crate::pretty_print_as_debug)
409/// and [`print_as_debug`](crate::Generator::print_as_debug); it is exposed
410/// for hand-written [`PrettyPrintable`] implementations that want to embed a
411/// `Debug` representation in a larger layout.
412pub fn print_debug_repr(repr: &str, printer: &mut PrettyPrinter) {
413    match DebugRepr::parse(repr) {
414        Some(nodes) => emit_debug_nodes(&nodes, printer),
415        None => printer.text(repr),
416    }
417}
418
419/// One parsed piece of a `Debug` representation: literal text, or a
420/// delimited group laid out with a breakable point after each comma.
421enum DebugNode {
422    Leaf(String),
423    Group {
424        /// The atom glued to the open delimiter (`Some` in `Some(5)`, `Name`
425        /// in `Name { … }`); empty for bare tuples, lists, and map braces.
426        prefix: String,
427        delimiter: char,
428        /// Brace group in derived struct style (`Name { … }`, spaces inside
429        /// the braces) as opposed to map style (`{… }`).
430        named: bool,
431        items: Vec<Vec<DebugNode>>,
432    },
433}
434
435/// Recursive-descent parser over the derived-`Debug` grammar. Any input
436/// outside the grammar makes a parsing method return `None`, and the whole
437/// representation falls back to verbatim text.
438struct DebugRepr {
439    chars: Vec<char>,
440    pos: usize,
441    depth: usize,
442}
443
444/// How deeply groups may nest before [`DebugRepr::parse`] gives up. The
445/// parser, the emitter, and the parsed tree's destructor all recurse
446/// per nesting level, so an unbounded representation would overflow the
447/// stack during failure reporting; past this depth the representation is
448/// emitted verbatim instead.
449const MAX_DEBUG_DEPTH: usize = 64;
450
451impl DebugRepr {
452    fn parse(repr: &str) -> Option<Vec<DebugNode>> {
453        if repr.contains('\n') {
454            return None;
455        }
456        let mut parser = DebugRepr {
457            chars: repr.chars().collect(),
458            pos: 0,
459            depth: 0,
460        };
461        let nodes = parser.parse_item()?;
462        if parser.pos != parser.chars.len() {
463            return None;
464        }
465        Some(nodes)
466    }
467
468    fn peek(&self) -> Option<char> {
469        self.chars.get(self.pos).copied()
470    }
471
472    fn peek_next(&self) -> Option<char> {
473        self.chars.get(self.pos + 1).copied()
474    }
475
476    fn bump(&mut self) -> Option<char> {
477        let c = self.peek()?;
478        self.pos += 1;
479        Some(c)
480    }
481
482    /// Parse one comma-separated item — literal runs and nested groups —
483    /// stopping (without consuming) at a `", "`, a close delimiter, or the
484    /// end of the input.
485    fn parse_item(&mut self) -> Option<Vec<DebugNode>> {
486        let mut nodes = Vec::new();
487        let mut text = String::new();
488        loop {
489            match self.peek() {
490                None | Some(']' | ')' | '}') => break,
491                Some(',') if self.peek_next() == Some(' ') => break,
492                Some(' ') if self.peek_next() == Some('}') => break,
493                Some('"' | '\'') => {
494                    flush_text(&mut text, &mut nodes);
495                    nodes.push(DebugNode::Leaf(self.lex_quoted()?));
496                }
497                Some(delimiter @ ('[' | '(' | '{')) => {
498                    let prefix = take_group_prefix(&mut text, delimiter);
499                    flush_text(&mut text, &mut nodes);
500                    nodes.push(self.parse_group(prefix)?);
501                }
502                Some(c) => {
503                    text.push(c);
504                    self.bump();
505                }
506            }
507        }
508        flush_text(&mut text, &mut nodes);
509        Some(nodes)
510    }
511
512    /// Parse a delimited group whose open delimiter is the current char.
513    fn parse_group(&mut self, prefix: String) -> Option<DebugNode> {
514        if self.depth == MAX_DEBUG_DEPTH {
515            return None;
516        }
517        self.depth += 1;
518        let delimiter = self.bump()?;
519        let close = match delimiter {
520            '[' => ']',
521            '(' => ')',
522            _ => '}',
523        };
524        let named = delimiter == '{' && !prefix.is_empty() && self.peek() == Some(' ');
525        if named {
526            self.bump();
527        }
528        let mut items = Vec::new();
529        if !named && self.peek() == Some(close) {
530            self.bump();
531        } else {
532            loop {
533                items.push(self.parse_item()?);
534                match self.peek() {
535                    Some(',') if self.peek_next() == Some(' ') => {
536                        self.bump();
537                        self.bump();
538                    }
539                    Some(' ') if named && self.peek_next() == Some(close) => {
540                        self.bump();
541                        self.bump();
542                        break;
543                    }
544                    Some(c) if !named && c == close => {
545                        self.bump();
546                        break;
547                    }
548                    _ => return None,
549                }
550            }
551        }
552        self.depth -= 1;
553        Some(DebugNode::Group {
554            prefix,
555            delimiter,
556            named,
557            items,
558        })
559    }
560
561    /// Lex a string or character literal, including its quotes. A backslash
562    /// escapes the following character, which is all the lexer needs: no
563    /// escape sequence contains an unescaped closing quote.
564    fn lex_quoted(&mut self) -> Option<String> {
565        let quote = self.bump()?;
566        let mut lit = String::new();
567        lit.push(quote);
568        loop {
569            let c = self.bump()?;
570            lit.push(c);
571            if c == '\\' {
572                lit.push(self.bump()?);
573            } else if c == quote {
574                return Some(lit);
575            }
576        }
577    }
578}
579
580/// Move accumulated literal text into a leaf node.
581fn flush_text(text: &mut String, nodes: &mut Vec<DebugNode>) {
582    if !text.is_empty() {
583        nodes.push(DebugNode::Leaf(std::mem::take(text)));
584    }
585}
586
587/// Split the atom glued to an open delimiter off the accumulated text:
588/// `Some` from `Some(`, and `Name` (dropping the joining space) from
589/// `Name {`. Brace groups only take a prefix across that space — a brace
590/// directly following text is not the derived-struct shape.
591fn take_group_prefix(text: &mut String, delimiter: char) -> String {
592    if delimiter == '{' {
593        let Some(without_space) = text.strip_suffix(' ') else {
594            return String::new();
595        };
596        let start = without_space.rfind(' ').map(|index| index + 1).unwrap_or(0);
597        let prefix = without_space[start..].to_string();
598        if prefix.is_empty() {
599            return String::new();
600        }
601        text.truncate(text.len() - prefix.len() - 1);
602        prefix
603    } else {
604        let start = text.rfind(' ').map(|index| index + 1).unwrap_or(0);
605        let prefix = text[start..].to_string();
606        text.truncate(start);
607        prefix
608    }
609}
610
611/// Emit parsed nodes, matching the layout `#[derive(PrettyPrintable)]`
612/// produces for the same shapes.
613fn emit_debug_nodes(nodes: &[DebugNode], printer: &mut PrettyPrinter) {
614    for node in nodes {
615        match node {
616            DebugNode::Leaf(text) => printer.text(text),
617            DebugNode::Group {
618                prefix,
619                delimiter,
620                named,
621                items,
622            } => {
623                let (open, close, indent) = match (delimiter, named) {
624                    ('{', true) => (format!("{prefix} {{"), " }", 4),
625                    ('{', false) if prefix.is_empty() => ("{".to_string(), "}", 1),
626                    ('{', false) => (format!("{prefix} {{"), "}", 1),
627                    ('[', _) => (format!("{prefix}["), "]", 1),
628                    _ => (format!("{prefix}("), ")", 1),
629                };
630                printer.begin_group(indent, &open);
631                if *named {
632                    printer.breakable(" ");
633                }
634                for (index, item) in items.iter().enumerate() {
635                    if index > 0 {
636                        printer.text(",");
637                        printer.breakable(" ");
638                    }
639                    emit_debug_nodes(item, printer);
640                }
641                printer.end_group(close);
642            }
643        }
644    }
645}
646
647/// A value that can describe its own printed representation.
648///
649/// Implementations should print the value in Rust-expression syntax wherever
650/// possible, so a reported failing example can be pasted back into code, and
651/// should express any internal structure through the printer's group and
652/// breakable primitives so large values wrap readably.
653///
654/// Provided for the standard types the generator library produces. For user
655/// types, either `#[derive(PrettyPrintable)]` or — to reuse an existing
656/// `Debug` representation without writing anything —
657/// [`pretty_print_as_debug!`](crate::pretty_print_as_debug).
658///
659/// `HashMap` and `HashSet` print as `HashMap::from([…])` /
660/// `HashSet::from([…])`, expressions that only construct the default-hasher
661/// types, so maps and sets with a custom hasher are deliberately not
662/// printable — print those through
663/// [`print_as_debug`](crate::generators::Generator::print_as_debug) or
664/// [`print_with`](crate::generators::Generator::print_with) instead:
665///
666/// ```compile_fail,E0277
667/// use std::collections::{HashMap, HashSet};
668/// use std::hash::{BuildHasherDefault, DefaultHasher};
669///
670/// fn assert_printable<T: hegel::PrettyPrintable>() {}
671/// assert_printable::<HashSet<i32, BuildHasherDefault<DefaultHasher>>>();
672/// assert_printable::<HashMap<i32, bool, BuildHasherDefault<DefaultHasher>>>();
673/// ```
674#[diagnostic::on_unimplemented(
675    message = "`{Self}` has no printed representation",
676    label = "`{Self}` does not implement `PrettyPrintable`",
677    note = "for your own type, add `#[derive(PrettyPrintable)]` (or `hegel::pretty_print_as_debug!` for a `Debug` type)",
678    note = "for a foreign type, make the generator printable instead: `.print_as_debug()` prints any `Debug` value, `.print_with(..)` prints a custom representation",
679    note = "or draw without reporting the value via `tc.draw_silent(..)`"
680)]
681pub trait PrettyPrintable {
682    /// Print this value's representation to `printer`.
683    fn pretty_print(&self, printer: &mut PrettyPrinter);
684}
685
686/// Implement [`PrettyPrintable`] for one or more local `Debug` types by
687/// printing their `{:?}` representation through
688/// [`print_debug_repr`](crate::pretty::print_debug_repr), so derived-`Debug`
689/// output wraps like a native implementation.
690///
691/// This is for **your own types** whose `Debug` output is already the
692/// representation you want: the orphan rule means it cannot implement a
693/// hegel trait for a type from another crate (including the standard
694/// library). To print a foreign type by its `Debug` representation, make
695/// the *generator* printable instead with
696/// [`print_as_debug`](crate::Generator::print_as_debug).
697///
698/// ```
699/// use hegel::{Document, PrettyPrintable};
700///
701/// #[derive(Debug)]
702/// struct Point {
703///     x: i32,
704///     y: i32,
705/// }
706/// hegel::pretty_print_as_debug!(Point);
707///
708/// let mut doc = Document::new();
709/// Point { x: 1, y: 2 }.pretty_print(doc.printer());
710/// assert_eq!(doc.finish(), "Point { x: 1, y: 2 }");
711/// ```
712#[macro_export]
713macro_rules! pretty_print_as_debug {
714    ($($t:ty),+ $(,)?) => {$(
715        impl $crate::PrettyPrintable for $t {
716            fn pretty_print(&self, printer: &mut $crate::PrettyPrinter) {
717                $crate::pretty::print_debug_repr(&::std::format!("{:?}", self), printer);
718            }
719        }
720    )+};
721}
722
723macro_rules! pretty_via_display {
724    ($($t:ty),+) => {$(
725        impl PrettyPrintable for $t {
726            fn pretty_print(&self, printer: &mut PrettyPrinter) {
727                printer.text(&format!("{}", self));
728            }
729        }
730    )+};
731}
732
733pretty_via_display!(
734    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool
735);
736
737macro_rules! pretty_via_debug {
738    ($($t:ty),+) => {$(
739        impl PrettyPrintable for $t {
740            fn pretty_print(&self, printer: &mut PrettyPrinter) {
741                printer.text(&format!("{:?}", self));
742            }
743        }
744    )+};
745}
746
747pretty_via_debug!(char, str);
748
749impl PrettyPrintable for String {
750    fn pretty_print(&self, printer: &mut PrettyPrinter) {
751        printer.text(&format!("{self:?}.to_string()"));
752    }
753}
754
755impl PrettyPrintable for std::time::Duration {
756    fn pretty_print(&self, printer: &mut PrettyPrinter) {
757        printer.text(&format!(
758            "Duration::new({}, {})",
759            self.as_secs(),
760            self.subsec_nanos()
761        ));
762    }
763}
764
765impl PrettyPrintable for std::net::Ipv4Addr {
766    fn pretty_print(&self, printer: &mut PrettyPrinter) {
767        let [a, b, c, d] = self.octets();
768        printer.text(&format!("Ipv4Addr::new({a}, {b}, {c}, {d})"));
769    }
770}
771
772impl PrettyPrintable for std::net::Ipv6Addr {
773    fn pretty_print(&self, printer: &mut PrettyPrinter) {
774        let segments = self
775            .segments()
776            .map(|segment| format!("{segment:#x}"))
777            .join(", ");
778        printer.text(&format!("Ipv6Addr::new({segments})"));
779    }
780}
781
782impl PrettyPrintable for std::net::IpAddr {
783    fn pretty_print(&self, printer: &mut PrettyPrinter) {
784        match self {
785            std::net::IpAddr::V4(addr) => {
786                printer.text("IpAddr::V4(");
787                addr.pretty_print(printer);
788                printer.text(")");
789            }
790            std::net::IpAddr::V6(addr) => {
791                printer.text("IpAddr::V6(");
792                addr.pretty_print(printer);
793                printer.text(")");
794            }
795        }
796    }
797}
798
799macro_rules! pretty_float {
800    ($t:ty, $name:literal) => {
801        impl PrettyPrintable for $t {
802            fn pretty_print(&self, printer: &mut PrettyPrinter) {
803                if self.is_nan() {
804                    if self.to_bits() == <$t>::NAN.to_bits() {
805                        printer.text(concat!($name, "::NAN"));
806                    } else {
807                        printer.text(&format!(
808                            concat!($name, "::from_bits(0x{:x})"),
809                            self.to_bits()
810                        ));
811                    }
812                } else if *self == <$t>::INFINITY {
813                    printer.text(concat!($name, "::INFINITY"));
814                } else if *self == <$t>::NEG_INFINITY {
815                    printer.text(concat!($name, "::NEG_INFINITY"));
816                } else {
817                    printer.text(&format!("{:?}", self));
818                }
819            }
820        }
821    };
822}
823
824pretty_float!(f32, "f32");
825pretty_float!(f64, "f64");
826
827macro_rules! pretty_delegating {
828    ($($t:ty),+) => {$(
829        impl<T: PrettyPrintable + ?Sized> PrettyPrintable for $t {
830            fn pretty_print(&self, printer: &mut PrettyPrinter) {
831                (**self).pretty_print(printer);
832            }
833        }
834    )+};
835}
836
837pretty_delegating!(&T, &mut T);
838
839macro_rules! pretty_smart_pointer {
840    ($($t:ty, $open:literal);+) => {$(
841        impl<T: PrettyPrintable> PrettyPrintable for $t {
842            fn pretty_print(&self, printer: &mut PrettyPrinter) {
843                printer.begin_group($open.len(), $open);
844                (**self).pretty_print(printer);
845                printer.end_group(")");
846            }
847        }
848    )+};
849}
850
851pretty_smart_pointer!(
852    Box<T>, "Box::new(";
853    std::rc::Rc<T>, "Rc::new(";
854    std::sync::Arc<T>, "Arc::new("
855);
856
857/// `Box::new` cannot build a boxed unsized value, so `Box<str>` prints its
858/// target instead of a constructor.
859impl PrettyPrintable for Box<str> {
860    fn pretty_print(&self, printer: &mut PrettyPrinter) {
861        (**self).pretty_print(printer);
862    }
863}
864
865/// Print `items` as a delimited, comma-separated sequence: inline when it
866/// fits, one element per line (aligned just inside `open`) when it does not.
867fn pretty_seq<'a, T: PrettyPrintable + ?Sized + 'a>(
868    printer: &mut PrettyPrinter,
869    open: &str,
870    close: &str,
871    items: impl Iterator<Item = &'a T>,
872) {
873    printer.begin_group(open.chars().count(), open);
874    for (index, item) in items.enumerate() {
875        if index > 0 {
876            printer.text(",");
877            printer.breakable(" ");
878        }
879        item.pretty_print(printer);
880    }
881    printer.end_group(close);
882}
883
884impl<T: PrettyPrintable> PrettyPrintable for [T] {
885    fn pretty_print(&self, printer: &mut PrettyPrinter) {
886        pretty_seq(printer, "[", "]", self.iter());
887    }
888}
889
890impl<T: PrettyPrintable> PrettyPrintable for Vec<T> {
891    fn pretty_print(&self, printer: &mut PrettyPrinter) {
892        pretty_seq(printer, "vec![", "]", self.iter());
893    }
894}
895
896impl<T: PrettyPrintable, const N: usize> PrettyPrintable for [T; N] {
897    fn pretty_print(&self, printer: &mut PrettyPrinter) {
898        self.as_slice().pretty_print(printer);
899    }
900}
901
902impl<T: PrettyPrintable> PrettyPrintable for std::collections::HashSet<T> {
903    fn pretty_print(&self, printer: &mut PrettyPrinter) {
904        pretty_seq(printer, "HashSet::from([", "])", self.iter());
905    }
906}
907
908impl<T: PrettyPrintable> PrettyPrintable for std::collections::BTreeSet<T> {
909    fn pretty_print(&self, printer: &mut PrettyPrinter) {
910        pretty_seq(printer, "BTreeSet::from([", "])", self.iter());
911    }
912}
913
914/// Print `entries` as a `Name::from([(key, value), …])` map: inline when it
915/// fits, one entry per line when it does not.
916fn pretty_map<'a, K: PrettyPrintable + 'a, V: PrettyPrintable + 'a>(
917    printer: &mut PrettyPrinter,
918    open: &str,
919    entries: impl Iterator<Item = (&'a K, &'a V)>,
920) {
921    printer.begin_group(open.chars().count(), open);
922    for (index, (key, value)) in entries.enumerate() {
923        if index > 0 {
924            printer.text(",");
925            printer.breakable(" ");
926        }
927        printer.text("(");
928        key.pretty_print(printer);
929        printer.text(", ");
930        value.pretty_print(printer);
931        printer.text(")");
932    }
933    printer.end_group("])");
934}
935
936impl<K: PrettyPrintable, V: PrettyPrintable> PrettyPrintable for std::collections::HashMap<K, V> {
937    fn pretty_print(&self, printer: &mut PrettyPrinter) {
938        pretty_map(printer, "HashMap::from([", self.iter());
939    }
940}
941
942impl<K: PrettyPrintable, V: PrettyPrintable> PrettyPrintable for std::collections::BTreeMap<K, V> {
943    fn pretty_print(&self, printer: &mut PrettyPrinter) {
944        pretty_map(printer, "BTreeMap::from([", self.iter());
945    }
946}
947
948impl<T: PrettyPrintable> PrettyPrintable for Option<T> {
949    fn pretty_print(&self, printer: &mut PrettyPrinter) {
950        match self {
951            None => printer.text("None"),
952            Some(value) => {
953                printer.begin_group(5, "Some(");
954                value.pretty_print(printer);
955                printer.end_group(")");
956            }
957        }
958    }
959}
960
961impl<T: PrettyPrintable, E: PrettyPrintable> PrettyPrintable for Result<T, E> {
962    fn pretty_print(&self, printer: &mut PrettyPrinter) {
963        match self {
964            Ok(value) => {
965                printer.begin_group(3, "Ok(");
966                value.pretty_print(printer);
967                printer.end_group(")");
968            }
969            Err(error) => {
970                printer.begin_group(4, "Err(");
971                error.pretty_print(printer);
972                printer.end_group(")");
973            }
974        }
975    }
976}
977
978impl PrettyPrintable for () {
979    fn pretty_print(&self, printer: &mut PrettyPrinter) {
980        printer.text("()");
981    }
982}
983
984impl<A: PrettyPrintable> PrettyPrintable for (A,) {
985    fn pretty_print(&self, printer: &mut PrettyPrinter) {
986        printer.begin_group(1, "(");
987        self.0.pretty_print(printer);
988        printer.end_group(",)");
989    }
990}
991
992macro_rules! pretty_tuple {
993    ($(($($name:ident),+)),+ $(,)?) => {$(
994        #[allow(non_snake_case)]
995        impl<$($name: PrettyPrintable),+> PrettyPrintable for ($($name,)+) {
996            fn pretty_print(&self, printer: &mut PrettyPrinter) {
997                let ($($name,)+) = self;
998                printer.begin_group(1, "(");
999                let mut index = 0usize;
1000                $(
1001                    if index > 0 {
1002                        printer.text(",");
1003                        printer.breakable(" ");
1004                    }
1005                    index += 1;
1006                    $name.pretty_print(printer);
1007                )+
1008                let _ = index;
1009                printer.end_group(")");
1010            }
1011        }
1012    )+};
1013}
1014
1015pretty_tuple!(
1016    (A, B),
1017    (A, B, C),
1018    (A, B, C, D),
1019    (A, B, C, D, E),
1020    (A, B, C, D, E, F),
1021    (A, B, C, D, E, F, G),
1022    (A, B, C, D, E, F, G, H),
1023    (A, B, C, D, E, F, G, H, I),
1024    (A, B, C, D, E, F, G, H, I, J),
1025    (A, B, C, D, E, F, G, H, I, J, K),
1026    (A, B, C, D, E, F, G, H, I, J, K, L),
1027);