From 178267c701c233542078c09fe6b19802f9642dbd Mon Sep 17 00:00:00 2001 From: HampusM Date: Mon, 30 Jan 2023 21:29:21 +0100 Subject: feat: improve macro error messages --- macros/src/util/error.rs | 116 ++++++++++++++++++++++++++++++++++++++++ macros/src/util/iterator_ext.rs | 42 ++++++++++----- macros/src/util/mod.rs | 23 ++++++++ 3 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 macros/src/util/error.rs (limited to 'macros/src/util') diff --git a/macros/src/util/error.rs b/macros/src/util/error.rs new file mode 100644 index 0000000..d068661 --- /dev/null +++ b/macros/src/util/error.rs @@ -0,0 +1,116 @@ +/// Used to create a error enum that converts into a [`Diagnostic`]. +/// +/// [`Diagnostic`]: proc_macro_error::Diagnostic +macro_rules! diagnostic_error_enum { + ($(#[$meta: meta])* $visibility: vis enum $name: ident { + $( + #[error($($error: tt)*), span = $error_span: expr] + $(#[note($($note: tt)*)$(, span = $note_span: expr)?])* + $(#[help($($help: tt)*)$(, span = $help_span: expr)?])* + $(#[err($($err: tt)*)$(, span = $err_span: expr)?])* + $(#[source($source: ident)])? + $variant: ident { + $($variant_field: ident: $variant_field_type: ty),* + }, + )* + }) => { + $(#[$meta])* + #[derive(Debug, Clone)] + $visibility enum $name + { + $( + $variant { + $($variant_field: $variant_field_type),* + }, + )* + } + + impl From<$name> for ::proc_macro_error::Diagnostic + { + #[must_use] + fn from(err: $name) -> Self + { + let (error, span, notes, helps, errs, source): ( + String, + ::proc_macro2::Span, + Vec<(String, ::proc_macro2::Span)>, + Vec<(String, ::proc_macro2::Span)>, + Vec<(String, ::proc_macro2::Span)>, + Option<::proc_macro_error::Diagnostic> + ) = match err { + $( + $name::$variant { + $($variant_field),* + } => { + ( + format!($($error)*), + $error_span, + vec![$( + ( + format!($($note)*), + $crate::util::or!( + ($($note_span)?) + else (::proc_macro2::Span::call_site()) + ) + ) + ),*], + vec![$( + ( + format!($($help)*), + $crate::util::or!( + ($($help_span)?) + else (::proc_macro2::Span::call_site()) + ) + ) + ),*], + vec![$( + ( + format!($($err)*), + $crate::util::or!( + ($($err_span)?) + else (::proc_macro2::Span::call_site()) + ) + ) + ),*], + $crate::util::to_option!($($source.into())?) + ) + } + ),* + }; + + if let Some(source_diagnostic) = source { + source_diagnostic.emit(); + } + + let mut diagnostic = ::proc_macro_error::Diagnostic::spanned( + span, + ::proc_macro_error::Level::Error, + error + ); + + if !notes.is_empty() { + for (note, note_span) in notes { + diagnostic = diagnostic.span_note(note_span, note); + } + } + + if !helps.is_empty() { + for (help, help_span) in helps { + diagnostic = diagnostic.span_help(help_span, help); + } + } + + if !errs.is_empty() { + for (err, err_span) in errs { + diagnostic = diagnostic.span_error(err_span, err); + } + } + + diagnostic + } + } + + }; +} + +pub(crate) use diagnostic_error_enum; diff --git a/macros/src/util/iterator_ext.rs b/macros/src/util/iterator_ext.rs index 5001068..17482ae 100644 --- a/macros/src/util/iterator_ext.rs +++ b/macros/src/util/iterator_ext.rs @@ -1,26 +1,39 @@ -use std::collections::HashMap; +use std::collections::HashSet; use std::hash::Hash; +/// [`Iterator`] extension trait. pub trait IteratorExt +where + Item: Eq + Hash, { - fn find_duplicate(&mut self) -> Option; + /// Finds the first occurance of a duplicate item. + /// + /// This function is short-circuiting. So it will immedietly return `Some` when + /// it comes across a item it has already seen. + /// + /// The returned tuple contains the first item occurance & the second item occurance. + /// In that specific order. + /// + /// Both items are returned in the case of the hash not being representative of the + /// whole item. + fn find_duplicate(self) -> Option<(Item, Item)>; } impl IteratorExt for Iter where Iter: Iterator, - Iter::Item: Eq + Hash + Clone, + Iter::Item: Eq + Hash, { - fn find_duplicate(&mut self) -> Option + fn find_duplicate(self) -> Option<(Iter::Item, Iter::Item)> { - let mut iterated_item_map = HashMap::::new(); + let mut iterated_item_map = HashSet::::new(); for item in self { - if iterated_item_map.contains_key(&item) { - return Some(item); + if let Some(equal_item) = iterated_item_map.take(&item) { + return Some((item, equal_item)); } - iterated_item_map.insert(item, ()); + iterated_item_map.insert(item); } None @@ -33,7 +46,7 @@ mod tests use super::*; #[test] - fn can_find_duplicate() + fn can_find_dupe() { #[derive(Debug, PartialEq, Eq, Clone, Hash)] struct Fruit @@ -58,9 +71,14 @@ mod tests ] .iter() .find_duplicate(), - Some(&Fruit { - name: "Apple".to_string() - }) + Some(( + &Fruit { + name: "Apple".to_string() + }, + &Fruit { + name: "Apple".to_string() + } + )) ); assert_eq!( diff --git a/macros/src/util/mod.rs b/macros/src/util/mod.rs index 0705853..d3edb67 100644 --- a/macros/src/util/mod.rs +++ b/macros/src/util/mod.rs @@ -1,4 +1,27 @@ +pub mod error; pub mod item_impl; pub mod iterator_ext; pub mod string; pub mod syn_path; + +macro_rules! to_option { + ($($tokens: tt)+) => { + Some($($tokens)+) + }; + + () => { + None + }; +} + +macro_rules! or { + (($($tokens: tt)+) else ($($default: tt)*)) => { + $($tokens)* + }; + + (() else ($($default: tt)*)) => { + $($default)* + }; +} + +pub(crate) use {or, to_option}; -- cgit v1.2.3-18-g5258