Skip to content

Commit

Permalink
[refactor] hyperledger-iroha#3882: Add tests for derive(HasOrigin) ma…
Browse files Browse the repository at this point in the history
…cro, fix error reporting on stable

Signed-off-by: Nikita Strygin <[email protected]>
  • Loading branch information
DCNick3 committed Sep 18, 2023
1 parent 67f89ce commit 435c721
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 7 deletions.
53 changes: 53 additions & 0 deletions data_model/derive/tests/has_origin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use iroha_data_model::prelude::{HasOrigin, Identifiable};
use iroha_data_model_derive::{HasOrigin, IdEqOrdHash};

#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
struct ObjectId(pub i32);

// fake impl for `#[derive(IdEqOrdHash)]`
impl From<ObjectId> for iroha_data_model::IdBox {
fn from(_: ObjectId) -> Self {
unimplemented!("fake impl")
}
}

#[derive(Debug, IdEqOrdHash)]
struct Object {
id: ObjectId,
}

impl Object {
fn id(&self) -> &ObjectId {
&self.id
}
}

#[allow(clippy::enum_variant_names)] // it's a test, duh
#[derive(Debug, HasOrigin)]
#[has_origin(origin = Object)]
enum ObjectEvent {
EventWithId(ObjectId),
#[has_origin(event => &event.0)]
EventWithExtractor((ObjectId, i32)),
#[has_origin(obj => obj.id())]
EventWithAnotherExtractor(Object),
}

#[test]
fn has_origin() {
let events = vec![
ObjectEvent::EventWithId(ObjectId(1)),
ObjectEvent::EventWithExtractor((ObjectId(2), 2)),
ObjectEvent::EventWithAnotherExtractor(Object { id: ObjectId(3) }),
];
let expected_ids = vec![ObjectId(1), ObjectId(2), ObjectId(3)];

for (event, expected_id) in events.into_iter().zip(expected_ids) {
assert_eq!(
event.origin_id(),
&expected_id,
"mismatched origin id for event {:?}",
event
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use iroha_data_model_derive::HasOrigin;

#[derive(HasOrigin)]
#[has_origin(origin = Object)]
#[has_origin(origin = Object)]
#[has_origin(origin = Object)]
enum MultipleAttributes {}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
error: Only one #[has_origin] attribute is allowed!
--> tests/ui_fail/has_origin_multiple_attributes.rs:5:1
|
5 | / #[has_origin(origin = Object)]
6 | | #[has_origin(origin = Object)]
| |______________________________^
51 changes: 44 additions & 7 deletions macro/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,46 @@ macro_rules! attr_struct {
};
}

/// Extension trait for [`darling::Error`].
///
/// Currently exists to add `with_spans` method.
pub trait DarlingErrorExt: Sized {
/// Attaches a combination of multiple spans to the error.
///
/// Note that it only attaches the first span on stable rustc, as the `Span::join` method is not yet stabilized (https://github.com/rust-lang/rust/issues/54725#issuecomment-649078500).
fn with_spans(self, spans: impl IntoIterator<Item = impl Into<proc_macro2::Span>>) -> Self;
}

impl DarlingErrorExt for darling::Error {
fn with_spans(self, spans: impl IntoIterator<Item = impl Into<proc_macro2::Span>>) -> Self {
// Unfortunately, the story for combining multiple spans in rustc proc macro is not yet complete.
// (see https://github.com/rust-lang/rust/issues/54725#issuecomment-649078500, https://github.com/rust-lang/rust/issues/54725#issuecomment-1547795742)
// syn does some hacks to get error reporting that is a bit better: https://docs.rs/syn/2.0.37/src/syn/error.rs.html#282
// we can't to that because darling's error type does not let us do that.

// on nightly, we are fine, as `.join` method works. On stable, we fall back to returning the first span.

let mut iter = spans.into_iter();
let Some(first) = iter.next() else {
return self;
};
let first: proc_macro2::Span = first.into();
let r = iter
.try_fold(first, |a, b| a.join(b.into()))
.unwrap_or(first);

self.with_span(&r)
}
}

/// Parses a single attribute of the form `#[attr_name(...)]` for darling using a `syn::parse::Parse` implementation.
///
/// If no attribute with specified name is found, returns `Ok(None)`.
///
/// # Errors
///
/// - If multiple attributes with specified name are found
/// - If attribute is not a list
pub fn parse_single_list_attr_opt<Body: syn2::parse::Parse>(
attr_name: &str,
attrs: &[syn2::Attribute],
Expand All @@ -93,13 +130,7 @@ pub fn parse_single_list_attr_opt<Body: syn2::parse::Parse>(
// allow parsing to proceed further to collect more errors
accumulator.push(
darling::Error::custom(format!("Only one #[{}] attribute is allowed!", attr_name))
.with_span(
&tail
.iter()
.map(syn2::spanned::Spanned::span)
.reduce(|a, b| a.join(b).unwrap())
.unwrap(),
),
.with_spans(tail.iter().map(syn2::spanned::Spanned::span)),
);
attr
}
Expand All @@ -122,6 +153,12 @@ pub fn parse_single_list_attr_opt<Body: syn2::parse::Parse>(
/// Parses a single attribute of the form `#[attr_name(...)]` for darling using a `syn::parse::Parse` implementation.
///
/// If no attribute with specified name is found, returns an error.
///
/// # Errors
///
/// - If multiple attributes with specified name are found
/// - If attribute is not a list
/// - If attribute is not found
pub fn parse_single_list_attr<Body: syn2::parse::Parse>(
attr_name: &str,
attrs: &[syn2::Attribute],
Expand Down

0 comments on commit 435c721

Please sign in to comment.