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}