Skip to content

Commit 36e3267

Browse files
committed
feat: Support omitting data parameter
Expect `(raw)` parameter in the `sv::payload` attribute.
1 parent fb28493 commit 36e3267

File tree

7 files changed

+202
-123
lines changed

7 files changed

+202
-123
lines changed

sylvia-derive/src/contract/communication/reply.rs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type};
66

77
use crate::crate_module;
88
use crate::parser::attributes::msg::ReplyOn;
9-
use crate::parser::{MsgType, ParsedSylviaAttributes, SylviaAttribute};
9+
use crate::parser::{MsgType, ParsedSylviaAttributes};
1010
use crate::types::msg_field::MsgField;
1111
use crate::types::msg_variant::{MsgVariant, MsgVariants};
1212
use crate::utils::emit_turbofish;
1313

14+
const NUMBER_OF_DATA_FIELDS: usize = 1;
15+
1416
pub struct Reply<'a> {
1517
source: &'a ItemImpl,
1618
generics: &'a [&'a GenericParam],
@@ -173,7 +175,7 @@ impl<'a> ReplyVariants<'a> for MsgVariants<'a, GenericParam> {
173175
},
174176
)
175177
}
176-
Some(existing_data) => existing_data.add_second_handler(handler),
178+
Some(existing_data) => existing_data.merge(handler),
177179
None => reply_data.push(ReplyData::new(reply_id, handler, handler_id)),
178180
}
179181
});
@@ -198,9 +200,13 @@ struct ReplyData<'a> {
198200

199201
impl<'a> ReplyData<'a> {
200202
pub fn new(reply_id: Ident, variant: &'a MsgVariant<'a>, handler_id: &'a Ident) -> Self {
201-
let data = variant.fields().first();
202-
// Skip the first field reserved for the `data`.
203-
let payload = variant.fields().iter().skip(1).collect::<Vec<_>>();
203+
let data = variant.as_data_field();
204+
let payload = variant.fields().iter();
205+
let payload = if data.is_some() || variant.msg_attr().reply_on() != ReplyOn::Success {
206+
payload.skip(NUMBER_OF_DATA_FIELDS).collect::<Vec<_>>()
207+
} else {
208+
payload.collect::<Vec<_>>()
209+
};
204210
let method_name = variant.function_name();
205211
let reply_on = variant.msg_attr().reply_on();
206212

@@ -214,13 +220,15 @@ impl<'a> ReplyData<'a> {
214220
}
215221

216222
/// Adds second handler to the reply data provdided their payload signature match.
217-
pub fn add_second_handler(&mut self, new_handler: &'a MsgVariant<'a>) {
223+
pub fn merge(&mut self, new_handler: &'a MsgVariant<'a>) {
218224
let (current_method_name, _) = match self.handlers.first() {
219225
Some(handler) => handler,
220226
_ => return,
221227
};
222228

223-
if self.payload.len() != new_handler.fields().len() - 1 {
229+
let new_reply_data = ReplyData::new(self.reply_id.clone(), new_handler, self.handler_id);
230+
231+
if self.payload.len() != new_reply_data.payload.len() {
224232
emit_error!(current_method_name.span(), "Mismatched quantity of method parameters.";
225233
note = self.handler_id.span() => format!("Both `{}` handlers should have the same number of parameters.", self.handler_id);
226234
note = new_handler.function_name().span() => format!("Previous definition of {} handler.", self.handler_id)
@@ -229,7 +237,7 @@ impl<'a> ReplyData<'a> {
229237

230238
self.payload
231239
.iter()
232-
.zip(new_handler.fields().iter().skip(1))
240+
.zip(new_reply_data.payload.iter())
233241
.for_each(|(current_field, new_field)|
234242
{
235243
if current_field.ty() != new_field.ty() {
@@ -377,6 +385,7 @@ impl<'a> ReplyData<'a> {
377385
let payload_values = self.payload.iter().map(|field| field.name());
378386
let payload_deserialization = self.payload.emit_payload_deserialization();
379387
let data_deserialization = self.data.map(DataField::emit_data_deserialization);
388+
let data = self.data.map(|_| quote! { data, });
380389

381390
quote! {
382391
#sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
@@ -385,7 +394,7 @@ impl<'a> ReplyData<'a> {
385394
#payload_deserialization
386395
#data_deserialization
387396

388-
#contract_turbofish ::new(). #method_name ((deps, env, gas_used, events, msg_responses).into(), data, #(#payload_values),* )
397+
#contract_turbofish ::new(). #method_name ((deps, env, gas_used, events, msg_responses).into(), #data #(#payload_values),* )
389398
}
390399
}
391400
}
@@ -462,6 +471,7 @@ impl<'a> ReplyData<'a> {
462471

463472
trait ReplyVariant<'a> {
464473
fn as_variant_handlers_pair(&'a self) -> Vec<(&'a MsgVariant<'a>, &'a Ident)>;
474+
fn as_data_field(&'a self) -> Option<&'a MsgField<'a>>;
465475
}
466476

467477
impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
@@ -479,6 +489,22 @@ impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
479489

480490
variant_handler_id_pair
481491
}
492+
493+
/// Returns `Some(MsgField)` if a field marked with `sv::data` attribute is present
494+
/// and the `reply_on` attribute is set to `ReplyOn::Success`.
495+
fn as_data_field(&'a self) -> Option<&'a MsgField<'a>> {
496+
let data_attrs = self.fields().first().map(|field| {
497+
ParsedSylviaAttributes::new(field.attrs().iter())
498+
.data
499+
.is_some()
500+
});
501+
match data_attrs {
502+
Some(attrs) if attrs && self.msg_attr().reply_on() == ReplyOn::Success => {
503+
self.fields().first()
504+
}
505+
_ => None,
506+
}
507+
}
482508
}
483509

484510
pub trait DataField {
@@ -489,10 +515,6 @@ impl DataField for MsgField<'_> {
489515
fn emit_data_deserialization(&self) -> TokenStream {
490516
let sylvia = crate_module();
491517
let data = ParsedSylviaAttributes::new(self.attrs().iter()).data;
492-
let is_data_attr = self
493-
.attrs()
494-
.iter()
495-
.any(|attr| SylviaAttribute::new(attr) == Some(SylviaAttribute::Data));
496518
let missing_data_err = "Missing reply data field.";
497519
let invalid_reply_data_err = quote! {
498520
format! {"Invalid reply data: {}\nSerde error while deserializing {}", data, err}
@@ -555,7 +577,7 @@ impl DataField for MsgField<'_> {
555577
None => None,
556578
};
557579
},
558-
None if is_data_attr => quote! {
580+
Some(_) => quote! {
559581
let data = match data {
560582
Some(data) => {
561583
#execute_data_deserialization
@@ -616,8 +638,11 @@ impl PayloadFields for Vec<&MsgField<'_>> {
616638
}
617639

618640
fn is_payload_marked(&self) -> bool {
619-
self.iter()
620-
.any(|field| field.contains_attribute(SylviaAttribute::Payload))
641+
self.iter().any(|field| {
642+
ParsedSylviaAttributes::new(field.attrs().iter())
643+
.payload
644+
.is_some()
645+
})
621646
}
622647
}
623648

sylvia-derive/src/parser/attributes/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
use data::DataFieldParams;
55
use features::SylviaFeatures;
6+
use payload::PayloadFieldParam;
67
use proc_macro_error::emit_error;
78
use syn::spanned::Spanned;
89
use syn::{Attribute, MetaList, PathSegment};
@@ -15,6 +16,7 @@ pub mod features;
1516
pub mod messages;
1617
pub mod msg;
1718
pub mod override_entry_point;
19+
pub mod payload;
1820

1921
pub use attr::{MsgAttrForwarding, VariantAttrForwarding};
2022
pub use custom::Custom;
@@ -79,6 +81,7 @@ pub struct ParsedSylviaAttributes {
7981
pub msg_attrs_forward: Vec<MsgAttrForwarding>,
8082
pub sv_features: SylviaFeatures,
8183
pub data: Option<DataFieldParams>,
84+
pub payload: Option<PayloadFieldParam>,
8285
}
8386

8487
impl ParsedSylviaAttributes {
@@ -90,6 +93,14 @@ impl ParsedSylviaAttributes {
9093

9194
if let (Some(sylvia_attr), Ok(attr)) = (sylvia_attr, &attr_content) {
9295
result.match_attribute(&sylvia_attr, attr);
96+
} else if sylvia_attr == Some(SylviaAttribute::Data) {
97+
// The `sv::data` attribute can be used without parameters.
98+
result.data = Some(DataFieldParams::default());
99+
} else if sylvia_attr == Some(SylviaAttribute::Payload) {
100+
emit_error!(
101+
attr.span(), "Missing parameters for `sv::payload`";
102+
note = "Expected `#[sv::payload(raw)]`"
103+
);
93104
}
94105
}
95106

@@ -172,10 +183,9 @@ impl ParsedSylviaAttributes {
172183
}
173184
}
174185
SylviaAttribute::Payload => {
175-
emit_error!(
176-
attr, "The attribute `sv::payload` used in wrong context";
177-
note = attr.span() => "The `sv::payload` should be used as a prefix for `Binary` payload.";
178-
);
186+
if let Ok(payload) = PayloadFieldParam::new(attr) {
187+
self.payload = Some(payload);
188+
}
179189
}
180190
SylviaAttribute::Data => {
181191
if let Ok(data) = DataFieldParams::new(attr) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use proc_macro_error::emit_error;
2+
use syn::parse::{Parse, ParseStream, Parser};
3+
use syn::{Error, Ident, MetaList, Result};
4+
5+
/// Type wrapping data parsed from `sv::payload` attribute.
6+
#[derive(Default, Debug)]
7+
pub struct PayloadFieldParam;
8+
9+
impl PayloadFieldParam {
10+
pub fn new(attr: &MetaList) -> Result<Self> {
11+
let data = PayloadFieldParam::parse
12+
.parse2(attr.tokens.clone())
13+
.map_err(|err| {
14+
emit_error!(err.span(), err);
15+
err
16+
})?;
17+
18+
Ok(data)
19+
}
20+
}
21+
22+
impl Parse for PayloadFieldParam {
23+
fn parse(input: ParseStream) -> Result<Self> {
24+
let option: Ident = input.parse()?;
25+
match option.to_string().as_str() {
26+
"raw" => (),
27+
_ => {
28+
return Err(Error::new(
29+
option.span(),
30+
"Invalid payload parameter.\n= note: Expected [`raw`].\n",
31+
))
32+
}
33+
};
34+
35+
if !input.is_empty() {
36+
return Err(Error::new(
37+
input.span(),
38+
"Unexpected tokens inside `sv::payload` attribute.\n= note: Expected parameters: [`raw`] `.\n",
39+
));
40+
}
41+
42+
Ok(Self)
43+
}
44+
}

sylvia-derive/src/types/msg_field.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::fold::StripSelfPath;
22
use crate::parser::check_generics::{CheckGenerics, GetPath};
3-
use crate::parser::SylviaAttribute;
43
use proc_macro2::TokenStream;
54
use proc_macro_error::emit_error;
65
use quote::quote;
@@ -124,10 +123,4 @@ impl<'a> MsgField<'a> {
124123
pub fn attrs(&self) -> &'a Vec<Attribute> {
125124
self.attrs
126125
}
127-
128-
pub fn contains_attribute(&self, sv_attr: SylviaAttribute) -> bool {
129-
self.attrs
130-
.iter()
131-
.any(|attr| SylviaAttribute::new(attr) == Some(sv_attr))
132-
}
133126
}

0 commit comments

Comments
 (0)