Skip to main content

mynt/
lib.rs

1#![doc = include_str!("../README.md")]
2//!
3//! # API
4//!
5//! The following are the utilities mynt provides to make error handling easier with proc-macros:
6//!
7//! ## Entry points
8//!
9//! Macros need to be wrapped in an entry point to use mynt's features (needed for quit + fallback support).
10//!
11//! - [`mynt!{}`](mynt): allows wrapping an inline/existing proc macros in lib.rs
12//! - [`mynt_macro!(name => name_impl);`](mynt_macro): declares a function-like proc macro
13//! - [`mynt_macro_attribute!(name => name_impl);`](mynt_macro_attribute): declares an attribute proc macro
14//! - [`mynt_macro_derive!(name for Trait(attributes(attr))? => name_impl);`](mynt_macro_derive):
15//! declares a derive proc macro
16//!
17//! ## Helpers
18//!
19//! Helpers make emitting diagnostics easy.
20//! They can be called in two ways:
21//! - `helper!(item);` for emitting a diagnostic from an item that implements [`Emittable`] (like strings or error types).
22//! - `helper!(spans => message);` for emitting a diagnostic with a custom span and message.
23//!
24//! You can also call `helper!("message");` to use the call site span.
25//!
26//! - [`emit!()`]: Emit a diagnostic for a given [`Level`]
27//! - [`help!()`]: Emit a help message (written to stderr on stable)
28//! - [`note!()`]: Emit a note (written to stderr on stable)
29//! - [`warn!()`]: Emit a warning (written to stderr on stable)
30//! - [`error!()`]: Emit an error
31//! - [`bail!()`]: Emit an error and return with the default value
32//! - [`fatal!()`]: Emit an error and [`quit`]
33//!
34//! ### Assertions
35//!
36//! mynt provides equivalents to `assert_*!` macros that instead
37//! call [`fatal!`] instead of [`panic!`] for cleaner error output.
38//!
39//! - [`mynt_assert!()`]: Ensures an expression is `true`
40//! - [`mynt_assert_eq!()`]: Ensures two expressions are equal
41//! - [`mynt_assert_ne!()`]: Ensures two expressions are not equal
42//!
43//! ### Low-level API
44//!
45//! mynt exposes some of its internals just in case.
46//!
47//! - [`Diagnostic`]: Manually write diagnostics
48//! - [`Level`]: The level of diagnostic (Error/Warning/Note/Help)
49//! - [`quit()`]: Quit the proc-macro and let mynt clean-up
50//!
51//! # Feature Flags
52//!
53//! - `default`: `proc-macro2`, `syn`
54//! - `darling`: support for darling error conversion
55//! - `nightly`: support for nightly Rust's `proc_macro_diagnostic` feature
56//! - `proc-macro2`: support for proc-macro2 span conversion
57//! - `syn`: support for syn error conversion
58//! - `venial`: support for venial error conversion
59//! - `yansi`: support for fallback (stable Rust) terminal coloring via yansi
60//!
61//! # Example
62//!
63//! This example (`/examples/attribute/src/lib.rs`) demonstrates the outer macro pattern,
64//! a technique where we want to share information between invocations of macros
65//! (like when we want to get information about items in a database schema),
66//! which is not currently possible with macros (without risking determinism).
67//!
68//! Instead, we can wrap macros in an *outer* macro, which can then find those macros,
69//! collect data from them, and then proceed with their implementations.
70//!
71//! This pattern shows how mynt can shine, by allowing inner macros to produce output,
72//! even if other inner macros run into errors.
73//!
74//! ```
75#![doc = include_str!("../example.rs")]
76//! ```
77//!
78//! Check out `/examples` on the repository to see how this macro is used and other examples.
79
80#![cfg_attr(feature = "nightly", feature(proc_macro_diagnostic))]
81
82extern crate proc_macro;
83
84#[cfg(not(feature = "nightly"))]
85pub mod fallback;
86
87/// Type alias to the available diagnostic struct.
88#[cfg(feature = "nightly")]
89pub type Diagnostic = proc_macro::Diagnostic;
90
91/// Type alias to the available diagnostic struct.
92#[cfg(not(feature = "nightly"))]
93pub type Diagnostic = fallback::Diagnostic;
94
95/// Type alias to the available diagnostic level enum.
96#[cfg(feature = "nightly")]
97pub type Level = proc_macro::Level;
98
99/// Type alias to the available diagnostic level enum.
100#[cfg(not(feature = "nightly"))]
101pub type Level = fallback::Level;
102
103/// Helper trait implemented by types that can be converted to a multispan.
104pub trait ToSpans {
105    fn to_spans(self) -> Vec<proc_macro::Span>;
106}
107
108impl ToSpans for proc_macro::Span {
109    fn to_spans(self) -> Vec<proc_macro::Span> {
110        vec![self]
111    }
112}
113
114#[cfg(feature = "proc-macro2")]
115impl ToSpans for proc_macro2::Span {
116    fn to_spans(self) -> Vec<proc_macro::Span> {
117        vec![self.unwrap()]
118    }
119}
120
121/// Trait implemented by types that can be emitted through diagnostics.
122pub trait Emittable {
123    /// Emits a [`Diagnostic`].
124    fn emit(level: Level, this: Self);
125}
126
127impl Emittable for &str {
128    fn emit(level: Level, this: Self) {
129        Diagnostic::spanned(proc_macro::Span::call_site(), level, this).emit();
130    }
131}
132
133impl Emittable for String {
134    fn emit(level: Level, this: Self) {
135        Diagnostic::spanned(proc_macro::Span::call_site(), level, this).emit();
136    }
137}
138
139#[cfg(feature = "syn")]
140impl Emittable for syn::Error {
141    fn emit(level: Level, this: Self) {
142        for err in this.into_iter() {
143            Diagnostic::spanned(err.span().unwrap(), level, err.to_string()).emit();
144        }
145    }
146}
147
148#[cfg(feature = "venial")]
149impl Emittable for venial::Error {
150    fn emit(level: Level, this: Self) {
151        // venial doesn't provide a way to iterate over errors,
152        // so this hack will suffice by filtering out string literals as messages
153        emit_compile_error_tokens_as_diagnostics(level, this.to_compile_error().into());
154    }
155}
156
157#[cfg(feature = "darling")]
158impl Emittable for darling_core::error::Error {
159    fn emit(level: Level, this: Self) {
160        // if darling has diagnostics enabled, we need to let it handle emitting them
161        // since there is no way to get the diagnostic level from darling directly
162        emit_compile_error_tokens_as_diagnostics(level, this.write_errors().into());
163        // we can't cfg(feature = "dep:darling_core/diagnostics"), but if we could,
164        // we would be able to iterate over non-diagnostic errors directly
165    }
166}
167
168/// Helper for emitting a diagnostic.
169///
170/// Has two forms (for macros based on this one, leave out `level`):
171/// - `($level:expr, $item:expr)`: emit a diagnostic with [`Level`] `level` and [`Emittable`] `item`
172/// - `($level:expr, $spans:expr => $message:expr)`:
173/// emit a diagnostic with [`Level`] `level` at [`ToSpans`] `spans` and [`Into<ToString>`] `message`.
174///
175/// For error types that can't implement [`Emittable`] (due to orphan rules),
176/// it is recommended to use the newtype pattern.
177#[macro_export]
178macro_rules! emit {
179    ($level:expr, $item:expr) => {{
180        $crate::Emittable::emit($level, $item);
181    }};
182    ($level:expr, $spans:expr => $message:expr) => {
183        $crate::Diagnostic::spanned($crate::ToSpans::to_spans($spans), $level, $message).emit()
184    };
185    ($level:expr, $spans:expr => $($message:tt)*) => {
186        $crate::Diagnostic::spanned($crate::ToSpans::to_spans($spans), $level, ::std::format!($($message)*)).emit()
187    };
188}
189
190/// Helper for emitting a [`Level::Help`] diagnostic.
191///
192/// See [`emit!()`] for usage.
193#[macro_export]
194macro_rules! help {
195    ($($input:tt)*) => ($crate::emit!($crate::Level::Help, $($input)*))
196}
197
198/// Helper for emitting a [`Level::Note`] diagnostic.
199///
200/// See [`emit!()`] for usage.
201#[macro_export]
202macro_rules! note {
203    ($($input:tt)*) => ($crate::emit!($crate::Level::Note, $($input)*))
204}
205
206/// Helper for emitting a [`Level::Warning`] diagnostic.
207///
208/// See [`emit!()`] for usage.
209#[macro_export]
210macro_rules! warn {
211    ($($input:tt)*) => ($crate::emit!($crate::Level::Warning, $($input)*))
212}
213
214/// Helper for emitting a [`Level::Error`] diagnostic.
215///
216/// See [`emit!()`] for usage.
217#[macro_export]
218macro_rules! error {
219    ($($input:tt)*) => ($crate::emit!($crate::Level::Error, $($input)*))
220}
221
222/// Emit an error diagnostic and then exit the current function with the [`Default::default`] value.
223///
224/// See [`emit!()`] for usage.
225#[macro_export]
226macro_rules! bail {
227    ($item:expr) => {{
228        $crate::Emittable::emit($crate::Level::Error, $item);
229        return ::core::default::Default::default();
230    }};
231    ($spans:expr => $message:expr) => {{
232        $crate::Diagnostic::spanned(
233            $crate::ToSpans::to_spans($spans),
234            $crate::Level::Error,
235            $message,
236        )
237        .emit();
238        return ::core::default::Default::default();
239    }};
240}
241
242/// Emit an error diagnostic and then [`quit`].
243///
244/// See [`emit!()`] for usage.
245#[macro_export]
246macro_rules! fatal {
247    ($item:expr) => {{
248        $crate::Emittable::emit($crate::Level::Error, $item);
249        $crate::quit();
250    }};
251    ($spans:expr => $message:expr) => {{
252        $crate::Diagnostic::spanned(
253            $crate::ToSpans::to_spans($spans),
254            $crate::Level::Error,
255            $message,
256        )
257        .emit();
258        $crate::quit();
259    }};
260}
261
262/// Asserts that a boolean expression is `true` at runtime.
263///
264/// This will invoke the [`fatal!`] macro if the provided expression
265/// cannot be evaluated to true at runtime.
266///
267/// # Custom Messages
268/// Like [`std::assert!`], it has a second form, where a custom error message can
269/// be provided with or without arguments for formatting. See [`std::fmt`]
270/// for syntax for this form. Expressions used as format arguments will only
271/// be evaluated if the assertion fails.
272#[macro_export]
273macro_rules! mynt_assert {
274    ($cond:expr $(,)?) => {{
275        if !($cond) {
276            $crate::fatal!(::core::stringify!($cond));
277        }
278    }};
279    ($cond:expr, $($arg:tt)+) => {{
280        if !($cond) {
281            $crate::fatal!(::std::format!($($arg:tt)+));
282        }
283    }};
284}
285
286/// Asserts that two expressions are equal to each other (using [`std::cmp::PartialEq`]).
287///
288/// Like [`mynt_assert!`], this macro has a second form, where a custom panic message can be provided.
289#[macro_export]
290macro_rules! mynt_assert_eq {
291    ($left:expr, $right:expr $(,)?) => {
292        match (&$left, &$right) {
293            (left_val, right_val) => {
294                if !(*left_val == *right_val) {
295                    let kind = $crate::AssertKind::Eq;
296                    $crate::assert_failed(
297                        kind,
298                        &*left_val,
299                        &*right_val,
300                        ::core::option::Option::None
301                    );
302                }
303            }
304        }
305    };
306    ($left:expr, $right:expr, $($arg:tt)+) => {
307        match (&$left, &$right) {
308            (left_val, right_val) => {
309                if !(*left_val == *right_val) {
310                    let kind = $crate::AssertKind::Eq;
311                    $crate::assert_failed(
312                        kind,
313                        &*left_val,
314                        &*right_val,
315                        ::core::option::Option::Some(::core::format_args!($($arg)+))
316                    );
317                }
318            }
319        }
320    };
321}
322
323/// Asserts that two expressions are not equal to each other (using [`std::cmp::PartialEq`]).
324///
325/// Like [`mynt_assert!`], this macro has a second form, where a custom panic message can be provided.
326#[macro_export]
327macro_rules! mynt_assert_ne {
328    ($left:expr, $right:expr $(,)?) => {
329        match (&$left, &$right) {
330            (left_val, right_val) => {
331                if !(*left_val != *right_val) {
332                    let kind = $crate::AssertKind::Ne;
333                    $crate::assert_failed(
334                        kind,
335                        &*left_val,
336                        &*right_val,
337                        ::core::option::Option::None
338                    );
339                }
340            }
341        }
342    };
343    ($left:expr, $right:expr, $($arg:tt)+) => {
344        match (&$left, &$right) {
345            (left_val, right_val) => {
346                if !(*left_val != *right_val) {
347                    let kind = $crate::AssertKind::Ne;
348                    $crate::assert_failed(
349                        kind,
350                        &*left_val,
351                        &*right_val,
352                        ::core::option::Option::Some(::core::format_args!($($arg)+))
353                    );
354                }
355            }
356        }
357    };
358}
359
360#[derive(Debug)]
361#[doc(hidden)]
362pub enum AssertKind {
363    Eq,
364    Ne,
365    Match, // TODO: maybe implement mynt_assert_matches!
366}
367
368#[doc(hidden)]
369pub fn assert_failed(
370    kind: AssertKind,
371    left: &dyn core::fmt::Debug,
372    right: &dyn core::fmt::Debug,
373    args: Option<core::fmt::Arguments<'_>>,
374) -> ! {
375    let op = match kind {
376        AssertKind::Eq => "==",
377        AssertKind::Ne => "!=",
378        AssertKind::Match => "matches",
379    };
380
381    match args {
382        Some(args) => {
383            Diagnostic::spanned(
384                proc_macro::Span::call_site(),
385                Level::Error,
386                format!(
387                    "assertion `left {op} right` failed: {args}\n  left: {left:?}\n right: {right:?}"
388                ),
389            )
390            .emit();
391            quit();
392        }
393        None => {
394            Diagnostic::spanned(
395                proc_macro::Span::call_site(),
396                Level::Error,
397                format!("assertion `left {op} right` failed:\n  left: {left:?}\n right: {right:?}"),
398            )
399            .emit();
400            quit();
401        }
402    }
403}
404
405/// A marker struct for panics originating from mynt interfaces.
406struct MyntPanicMarker;
407
408/// Panics with a marker that allows mynt to safely emit any errors accumulated
409/// before returning an empty token stream.
410pub fn quit() -> ! {
411    ::std::panic::panic_any(MyntPanicMarker);
412}
413
414// handles caught panics that originate from mynt
415#[doc(hidden)]
416pub fn quit_handler(err: Box<dyn std::any::Any + Send>) -> proc_macro::TokenStream {
417    if err.downcast_ref::<MyntPanicMarker>().is_some() {
418        proc_macro::TokenStream::new()
419    } else {
420        ::std::panic::resume_unwind(err)
421    }
422}
423
424/// Extension trait for [`Result`].
425pub trait MyntResultExt<T, E>: Sized {
426    /// Returns the contained [`Ok`] value or [`quit`]s the proc-macro.
427    ///
428    /// Quitting will emit the error as a diagnostic.
429    fn unwrap_or_quit(self) -> T
430    where
431        E: Emittable;
432
433    /// Returns the contained [`Ok`] value or returns a [`compile_error!`] token stream.
434    ///
435    /// The token stream is parsed for string literals which are emitted as diagnostics.
436    /// This is a fallback if [`MyntResultExt::unwrap_or_quit`] doesn't support
437    /// an error type, but a `to_compile_error` method is available.
438    fn unwrap_or_compile_error<F, R>(self, f: F) -> T
439    where
440        F: FnOnce(&E) -> R,
441        R: Into<proc_macro::TokenStream>;
442}
443
444impl<T, E> MyntResultExt<T, E> for Result<T, E> {
445    fn unwrap_or_quit(self) -> T
446    where
447        E: Emittable,
448    {
449        match self {
450            Ok(val) => val,
451            Err(err) => {
452                Emittable::emit(Level::Error, err);
453                quit();
454            }
455        }
456    }
457
458    fn unwrap_or_compile_error<F, R>(self, f: F) -> T
459    where
460        F: FnOnce(&E) -> R,
461        R: Into<proc_macro::TokenStream>,
462    {
463        match self {
464            Ok(val) => val,
465            Err(err) => {
466                let tokens: proc_macro::TokenStream = f(&err).into();
467                emit_compile_error_tokens_as_diagnostics(Level::Error, tokens);
468                std::panic::panic_any(MyntPanicMarker);
469            }
470        }
471    }
472}
473
474fn emit_compile_error_tokens_as_diagnostics(level: Level, tokens: proc_macro::TokenStream) {
475    fn handle_tt(level: Level, tt: proc_macro::TokenTree) {
476        match tt {
477            proc_macro::TokenTree::Group(group) => {
478                group
479                    .stream()
480                    .into_iter()
481                    .for_each(|tt| handle_tt(level, tt));
482            }
483            proc_macro::TokenTree::Literal(literal) => {
484                Diagnostic::spanned(literal.span(), level, literal.to_string()).emit();
485            }
486            _ => (),
487        }
488    }
489
490    tokens.into_iter().for_each(|tt| handle_tt(level, tt));
491}
492
493#[doc(hidden)]
494pub fn enter_diagnostics() {
495    #[cfg(not(feature = "nightly"))]
496    fallback::enter_diagnostics();
497}
498
499#[doc(hidden)]
500pub fn exit_diagnostics(tokens: &mut proc_macro::TokenStream) {
501    #[cfg(not(feature = "nightly"))]
502    fallback::exit_diagnostics(tokens);
503}
504
505#[doc(hidden)]
506#[macro_export]
507macro_rules! mynt_impl {
508    ($($call:tt)*) => {
509        {
510            $crate::enter_diagnostics();
511            let result = ::std::panic::catch_unwind(|| $($call)*);
512            let mut tokens: ::proc_macro::TokenStream = match result {
513                Ok(value) => value.into(),
514                Err(err) => $crate::quit_handler(err),
515            };
516            $crate::exit_diagnostics(&mut tokens);
517            tokens
518        }
519    };
520}
521
522/// General use entrypoint that wraps any proc-macro.
523#[macro_export]
524macro_rules! mynt {
525    {
526        $(#[$meta:meta])*
527        $vis:vis fn $name:ident
528        ($($arg_name:ident : $arg_type:ty),*)
529        -> $ret:ty
530        $body:block
531    } => {
532        $(#[$meta])*
533        $vis fn $name($($arg_name: ::proc_macro::TokenStream),*) -> ::proc_macro::TokenStream {
534            fn f($($arg_name: $arg_type),*) -> $ret $body
535            $crate::mynt_impl!(f($($arg_name.into()),*))
536        }
537    };
538}
539
540/// Declares a function-like proc-macro entrypoint.
541///
542/// # Usage
543///
544/// Calling `mynt_macro!(name => name_impl);` will declare a new macro called `name`
545/// which calls the implementation `name_impl`.
546///
547/// `name_impl` must have a signature compatible with
548/// `fn(impl Into<TokenStream>) -> impl Into<TokenStream>`.
549#[macro_export]
550macro_rules! mynt_macro {
551    ($name:ident => $f:ident) => {
552        #[proc_macro]
553        pub fn $name(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
554            $crate::mynt_impl!($f(input.into()))
555        }
556    };
557}
558
559/// Declares an attribute proc-macro entrypoint.
560///
561/// # Usage
562///
563/// Calling `mynt_macro_attribute!(name => name_impl);` will declare
564/// a new attribute macro called `name` which calls the implementation `name_impl`.
565///
566/// `name_impl` must have the signature compatible with
567/// `fn(impl Into<TokenStream>, impl Into<TokenStream>) -> impl Into<TokenStream>`.
568#[macro_export]
569macro_rules! mynt_macro_attribute {
570    ($name:ident => $f:ident) => {
571        #[proc_macro_attribute]
572        pub fn $name(
573            attr: ::proc_macro::TokenStream,
574            input: ::proc_macro::TokenStream,
575        ) -> ::proc_macro::TokenStream {
576            $crate::mynt_impl!($f(attr.into(), input.into()))
577        }
578    };
579}
580
581/// Declares a derive proc-macro entrypoint.
582///
583/// # Usage
584///
585/// Calling `mynt_macro_derive!(name for Trait => name_impl);` will declare
586/// a new derive macro called `name` which calls the implementation `name_impl`.
587///
588/// Helper attributes can also be declared by adding `(attributes(attrs))`
589/// after the `Trait`.
590///
591/// `name_impl` must have the signature compatible with
592/// `fn(impl Into<TokenStream>) -> impl Into<TokenStream>`.
593#[macro_export]
594macro_rules! mynt_macro_derive {
595    ($name:ident for $trait:ident $((attributes($($attr:ident),*)))? => $f:ident) => {
596        #[proc_macro_derive($trait $(, attributes($($attr),*))?)]
597        pub fn $name(
598            input: ::proc_macro::TokenStream,
599        ) -> ::proc_macro::TokenStream {
600            $crate::mynt_impl!($f(input.into()))
601        }
602    };
603}