hax_rust_engine/printer/
pretty_ast.rs

1//! Pretty-printing support for the hax AST.
2//!
3//! This module defines the trait [`PrettyAst`], which is the **primary trait a printer should
4//! implement**. It also exposes a handful of ergonomic helper macros that wire the
5//! [`pretty`](https://docs.rs/pretty/) crate's allocator into concise printing code, while
6//! taking care of annotations/spans for you.
7//!
8//! # Quickstart
9//! In most printers you:
10//! 1. Implement [`Printer`] for your allocator/type,
11//! 2. Implement [`PrettyAst`] for that allocator,
12//! 3. Call `x.pretty(&allocator)` on AST values.
13//!
14//! See [`super::backends`] for backend and printer examples.
15//!
16//! # Lifetimes and Parameters
17//! - `'a`: lifetime tied to the **document allocator** (from `pretty::DocAllocator`).
18//! - `'b`: lifetime of the **borrowed AST** node(s) being printed.
19//! - `A`: the **annotation** type carried in documents (e.g., spans for source maps).
20//!   `PrettyAst` is generic over `A`.
21
22use std::fmt::Display;
23
24use super::*;
25use crate::ast::*;
26use pretty::{DocAllocator, DocBuilder};
27
28use crate::symbol::Symbol;
29use identifiers::*;
30use literals::*;
31use resugared::*;
32
33/// This type is primarily useful inside printer implementations when you want a
34/// low-friction way to inspect an AST fragment.
35///
36/// # What it does
37/// - Appends a JSON representation of the wrapped value to
38///   `"/tmp/hax-ast-debug.json"` (one JSON document per line).
39/// - Implements [`std::fmt::Display`] to print a `just` invocation you can paste in a shell
40///   to re-open that same JSON by line number:
41///   `just debug-json <line-id>`
42///
43/// # Example
44/// ```rust
45/// # use hax_rust_engine::printer::pretty_ast::DebugJSON;
46/// # #[derive(serde::Serialize)]
47/// # struct Small { x: u32 }
48/// let s = Small { x: 42 };
49/// // Prints something like: `just debug-json 17`.
50/// println!("{}", DebugJSON(&s));
51/// // Running `just debug-json 17` will print `{"x":42}`
52/// ```
53///
54/// # Notes
55/// - This is a **debugging convenience** and intentionally has a side-effect (file write).
56///   Avoid keeping it in user-facing output paths.
57/// - The file grows over time; occasionally delete it if you no longer need historical entries.
58pub struct DebugJSON<T: serde::Serialize>(pub T);
59
60impl<T: serde::Serialize> Display for DebugJSON<T> {
61    #[cfg(not(unix))]
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "<unknown, DebugJSON supported on unix plateforms only>")
64    }
65    #[cfg(unix)]
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        const PATH: &str = "/tmp/hax-ast-debug.json";
68        /// Write a new JSON as a line at the end of `PATH`
69        fn append_line_json(value: &serde_json::Value) -> std::io::Result<usize> {
70            use std::io::{BufRead, BufReader, Write};
71            cleanup();
72            let file = std::fs::OpenOptions::new()
73                .read(true)
74                .append(true)
75                .create(true)
76                .open(PATH)?;
77            let count = BufReader::new(&file).lines().count();
78            writeln!(&file, "{value}")?;
79            Ok(count)
80        }
81
82        /// Drop the file at `PATH` when we first write
83        fn cleanup() {
84            static DID_RUN: AtomicBool = AtomicBool::new(false);
85            use std::sync::atomic::{AtomicBool, Ordering};
86            if DID_RUN
87                .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
88                .is_ok()
89            {
90                let _ignored = std::fs::remove_file(PATH);
91            }
92        }
93
94        if let Ok(id) = append_line_json(&serde_json::to_value(&self.0).unwrap()) {
95            write!(f, "`just debug-json {id}`")
96        } else {
97            write!(f, "<DebugJSON failed>")
98        }
99    }
100}
101
102impl<'a, 'b, A: 'a + Clone, P: PrettyAst<'a, 'b, A>, T: 'b + serde::Serialize> Pretty<'a, P, A>
103    for DebugJSON<T>
104{
105    fn pretty(self, allocator: &'a P) -> DocBuilder<'a, P, A> {
106        allocator.as_string(format!("{self}"))
107    }
108}
109
110#[macro_export]
111/// Similar to [`std::todo`], but returns a document instead of panicking with a message.
112macro_rules! todo_document {
113    ($allocator:ident,) => {
114        {return $allocator.todo_document(&format!("TODO_LINE_{}", std::line!()));}
115    };
116    ($allocator:ident, $($tt:tt)*) => {
117        {
118            let message = format!($($tt)*);
119            return $allocator.todo_document(&message);
120        }
121    };
122}
123pub use todo_document;
124
125#[macro_export]
126/// Install pretty-printing helpers partially applied with a given local
127/// allocator.
128///
129/// This macro declares a set of small, local macros that proxy to the
130/// underlying [`pretty::DocAllocator`] methods and macro while capturing your
131/// allocator value. It keeps printing code concise and avoids passing the
132/// allocator around explicitly.
133///
134/// # Syntax
135/// ```rust,ignore
136/// install_pretty_helpers!(alloc_ident: AllocatorType)
137/// ```
138///
139/// - `alloc_ident`: the in-scope variable that implements both
140///   [`pretty::DocAllocator`] and [`Printer`].
141/// - `AllocatorType`: the concrete type of that variable.
142///
143/// # What gets installed
144/// - macro shorthands for common allocator methods:
145///   [`pretty::DocAllocator::nil`], [`pretty::DocAllocator::fail`],
146///   [`pretty::DocAllocator::hardline`], [`pretty::DocAllocator::space`],
147///   [`pretty::DocAllocator::line`], [`pretty::DocAllocator::line_`],
148///   [`pretty::DocAllocator::softline`], [`pretty::DocAllocator::softline_`],
149///   [`pretty::DocAllocator::as_string`], [`pretty::DocAllocator::text`],
150///   [`pretty::DocAllocator::concat`], [`pretty::DocAllocator::intersperse`],
151///   [`pretty::DocAllocator::column`], [`pretty::DocAllocator::nesting`],
152///   [`pretty::DocAllocator::reflow`].
153/// - a partially applied version of [`pretty::docs!`].
154/// - [`todo_document!`]: produce a placeholder document (that does not panic).
155macro_rules! install_pretty_helpers {
156    ($allocator:ident : $allocator_type:ty) => {
157        $crate::printer::pretty_ast::install_pretty_helpers!(
158            @$allocator,
159            #[doc = ::std::concat!("Proxy macro for [`", stringify!($crate), "::printer::pretty_ast::todo_document`] that automatically uses `", stringify!($allocator),"` as allocator.")]
160            disambiguated_todo{$crate::printer::pretty_ast::todo_document!},
161            #[doc = ::std::concat!("Proxy macro for [`pretty::docs`] that automatically uses `", stringify!($allocator),"` as allocator.")]
162            docs{pretty::docs!},
163            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::nil`] that automatically uses `", stringify!($allocator),"` as allocator.")]
164            nil{<$allocator_type as ::pretty::DocAllocator<'_, _>>::nil},
165            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::fail`] that automatically uses `", stringify!($allocator),"` as allocator.")]
166            fail{<$allocator_type as ::pretty::DocAllocator<'_, _>>::fail},
167            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::hardline`] that automatically uses `", stringify!($allocator),"` as allocator.")]
168            hardline{<$allocator_type as ::pretty::DocAllocator<'_, _>>::hardline},
169            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::space`] that automatically uses `", stringify!($allocator),"` as allocator.")]
170            space{<$allocator_type as ::pretty::DocAllocator<'_, _>>::space},
171            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::line`] that automatically uses `", stringify!($allocator),"` as allocator.")]
172            disambiguated_line{<$allocator_type as ::pretty::DocAllocator<'_, _>>::line},
173            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::line_`] that automatically uses `", stringify!($allocator),"` as allocator.")]
174            line_{<$allocator_type as ::pretty::DocAllocator<'_, _>>::line_},
175            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::softline`] that automatically uses `", stringify!($allocator),"` as allocator.")]
176            softline{<$allocator_type as ::pretty::DocAllocator<'_, _>>::softline},
177            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::softline_`] that automatically uses `", stringify!($allocator),"` as allocator.")]
178            softline_{<$allocator_type as ::pretty::DocAllocator<'_, _>>::softline_},
179            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::as_string`] that automatically uses `", stringify!($allocator),"` as allocator.")]
180            as_string{<$allocator_type as ::pretty::DocAllocator<'_, _>>::as_string},
181            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::text`] that automatically uses `", stringify!($allocator),"` as allocator.")]
182            text{<$allocator_type as ::pretty::DocAllocator<'_, _>>::text},
183            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::concat`] that automatically uses `", stringify!($allocator),"` as allocator.")]
184            disambiguated_concat{<$allocator_type as ::pretty::DocAllocator<'_, _>>::concat},
185            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::intersperse`] that automatically uses `", stringify!($allocator),"` as allocator.")]
186            intersperse{<$allocator_type as ::pretty::DocAllocator<'_, _>>::intersperse},
187            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::column`] that automatically uses `", stringify!($allocator),"` as allocator.")]
188            column{<$allocator_type as ::pretty::DocAllocator<'_, _>>::column},
189            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::nesting`] that automatically uses `", stringify!($allocator),"` as allocator.")]
190            nesting{<$allocator_type as ::pretty::DocAllocator<'_, _>>::nesting},
191            #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::reflow`] that automatically uses `", stringify!($allocator),"` as allocator.")]
192            reflow{<$allocator_type as ::pretty::DocAllocator<'_, _>>::reflow}
193        );
194    };
195    (@$allocator:ident, $($(#[$($attrs:tt)*])*$name:ident{$($callable:tt)*}),*) => {
196        $(
197            #[hax_rust_engine_macros::partial_apply($($callable)*, $allocator,)]
198            #[allow(unused)]
199            $(#[$($attrs)*])*
200            macro_rules! $name {}
201        )*
202    };
203}
204pub use install_pretty_helpers;
205
206macro_rules! mk {
207    ($($ty:ident),*) => {
208        pastey::paste! {
209            /// A trait that defines a print method per type in the AST.
210            ///
211            /// This is the main trait a printer should implement. It ties
212            /// together:
213            /// - the [`pretty::DocAllocator`] implementation that builds
214            ///   documents,
215            /// - the [`Printer`] behavior (syntax highlighting, punctuation
216            ///   helpers, …),
217            /// - and annotation plumbing (`A`) used for source maps.
218            ///
219            /// ## Lifetimes and Type Parameters
220            /// - `'a`: the allocator/document lifetime.
221            /// - `'b`: the lifetime of borrowed AST values being printed.
222            /// - `A`: the annotation type carried by documents (must be
223            ///   `Clone`).
224            ///
225            /// ## Implementing `PrettyAst`
226            /// ```rust,ignore
227            /// impl<'a, 'b, A: 'a + Clone> PrettyAst<'a, 'b, A> for MyPrinter { }
228            /// ```
229            ///
230            /// You then implement the actual formatting logic in the generated
231            /// per-type methods. These methods are intentionally marked
232            /// `#[deprecated]` to discourage calling them directly; instead,
233            /// call `node.pretty(self)` from the [`pretty::Pretty`] trait to
234            /// ensure annotations and spans are applied correctly.
235            ///
236            /// Note that using `install_pretty_helpers!` will produce macro
237            /// that implicitely use `self` as allocator. Take a look at a
238            /// printer in the [`backends`] module for an example.
239            pub trait PrettyAst<'a, 'b, A: 'a + Clone>: DocAllocator<'a, A> + Sized {
240                /// Produce a non-panicking placeholder document. In general, prefer the use of the helper macro [`todo_document!`].
241                fn todo_document(&'a self, message: &str) -> DocBuilder<'a, Self, A> {
242                    self.as_string(message)
243                }
244                /// Produce a structured error document for an unimplemented
245                /// method.
246                ///
247                /// Printers may override this for nicer diagnostics (e.g.,
248                /// colored "unimplemented" banners or links back to source
249                /// locations). The default produces a small, debuggable piece
250                /// of text that includes the method name and a JSON handle for
251                /// the AST fragment (via [`DebugJSON`]).
252                fn unimplemented_method(&'a self, method: &str, ast: ast::fragment::FragmentRef<'_>) -> DocBuilder<'a, Self, A> {
253                    self.text(format!("`{method}` unimpl, {}", DebugJSON(ast))).parens()
254                }
255                $(
256                    #[doc = "Define how the printer formats a value of this AST type."]
257                    #[doc = "Do not call this method directly. Use [`pretty::Pretty::pretty`] instead, so annotations/spans are preserved correctly."]
258                    #[deprecated = "Do not call this method directly. Use [`pretty::Pretty::pretty`] instead, so annotations/spans are preserved correctly."]
259                    fn [<$ty:snake>](&'a self, [<$ty:snake>]: &'b $ty) -> DocBuilder<'a, Self, A> {
260                        mk!(@method_body $ty [<$ty:snake>] self [<$ty:snake>])
261                    }
262                )*
263            }
264
265            $(
266                impl<'a, 'b, A: 'a + Clone, P: PrettyAst<'a, 'b, A>> Pretty<'a, P, A> for &'b $ty {
267                    fn pretty(self, allocator: &'a P) -> DocBuilder<'a, P, A> {
268                        // Note about deprecation:
269                        //   Here is the only place where calling the deprecated methods from the trait `PrettyAst` is fine.
270                        //   Here is the place we (will) take care of spans, etc.
271                        #[allow(deprecated)]
272                        let print = <P as PrettyAst<'_, '_, _>>::[<$ty:snake>];
273                        print(allocator, self)
274                    }
275                }
276            )*
277        }
278    };
279    // Special default implementation for specific types
280    (@method_body Symbol $meth:ident $self:ident $value:ident) => {
281        $self.text($value.to_string())
282    };
283    (@method_body LocalId $meth:ident $self:ident $value:ident) => {
284        ::pretty::docs![$self, &$value.0]
285    };
286    (@method_body $ty:ident $meth:ident $self:ident $value:ident) => {
287        $self.unimplemented_method(stringify!($meth), ast::fragment::FragmentRef::from($meth))
288    };
289}
290
291#[hax_rust_engine_macros::replace(AstNodes => include(VisitableAstNodes))]
292mk!(GlobalId, AstNodes);