aboutsummaryrefslogtreecommitdiff
path: root/macros/src/util
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-01-30 21:29:21 +0100
committerHampusM <hampus@hampusmat.com>2023-01-30 21:29:21 +0100
commit178267c701c233542078c09fe6b19802f9642dbd (patch)
tree3d3010806f6509c062ca86dbbbe9c3a6cd2fe547 /macros/src/util
parent17ca46e95af38a914197958bbcc1e759865b6005 (diff)
feat: improve macro error messages
Diffstat (limited to 'macros/src/util')
-rw-r--r--macros/src/util/error.rs116
-rw-r--r--macros/src/util/iterator_ext.rs42
-rw-r--r--macros/src/util/mod.rs23
3 files changed, 169 insertions, 12 deletions
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<Item>
+where
+ Item: Eq + Hash,
{
- fn find_duplicate(&mut self) -> Option<Item>;
+ /// 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<Iter> IteratorExt<Iter::Item> for Iter
where
Iter: Iterator,
- Iter::Item: Eq + Hash + Clone,
+ Iter::Item: Eq + Hash,
{
- fn find_duplicate(&mut self) -> Option<Iter::Item>
+ fn find_duplicate(self) -> Option<(Iter::Item, Iter::Item)>
{
- let mut iterated_item_map = HashMap::<Iter::Item, ()>::new();
+ let mut iterated_item_map = HashSet::<Iter::Item>::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};