syn_solidity/item/
event.rs

1use crate::{
2    ParameterList, SolIdent, SolPath, Spanned, Type, VariableDeclaration, kw,
3    utils::DebugPunctuated,
4};
5use proc_macro2::Span;
6use std::fmt;
7use syn::{
8    Attribute, Error, Result, Token, parenthesized,
9    parse::{Parse, ParseStream},
10    punctuated::Punctuated,
11    token::Paren,
12};
13
14#[derive(Clone)]
15pub struct ItemEvent {
16    pub attrs: Vec<Attribute>,
17    pub event_token: kw::event,
18    pub name: SolIdent,
19    pub paren_token: Paren,
20    pub parameters: Punctuated<EventParameter, Token![,]>,
21    pub anonymous: Option<kw::anonymous>,
22    pub semi_token: Token![;],
23}
24
25impl fmt::Display for ItemEvent {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "event {}(", self.name)?;
28        for (i, param) in self.parameters.iter().enumerate() {
29            if i > 0 {
30                write!(f, ", ")?;
31            }
32            param.fmt(f)?;
33        }
34        f.write_str(")")?;
35        if self.is_anonymous() {
36            f.write_str(" anonymous")?;
37        }
38        f.write_str(";")
39    }
40}
41
42impl fmt::Debug for ItemEvent {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        f.debug_struct("ItemEvent")
45            .field("attrs", &self.attrs)
46            .field("name", &self.name)
47            .field("arguments", DebugPunctuated::new(&self.parameters))
48            .field("anonymous", &self.is_anonymous())
49            .finish()
50    }
51}
52
53impl Parse for ItemEvent {
54    fn parse(input: ParseStream<'_>) -> Result<Self> {
55        let content;
56        Ok(Self {
57            attrs: input.call(Attribute::parse_outer)?,
58            event_token: input.parse()?,
59            name: input.parse()?,
60            paren_token: parenthesized!(content in input),
61            parameters: content.parse_terminated(EventParameter::parse, Token![,])?,
62            anonymous: input.parse()?,
63            semi_token: input.parse()?,
64        })
65    }
66}
67
68impl Spanned for ItemEvent {
69    fn span(&self) -> Span {
70        self.name.span()
71    }
72
73    fn set_span(&mut self, span: Span) {
74        self.name.set_span(span);
75    }
76}
77
78impl ItemEvent {
79    /// Returns `true` if the event is anonymous.
80    #[inline]
81    pub const fn is_anonymous(&self) -> bool {
82        self.anonymous.is_some()
83    }
84
85    /// Returns the maximum amount of indexed parameters this event can have.
86    ///
87    /// This is `4` if the event is anonymous, otherwise `3`.
88    #[inline]
89    pub fn max_indexed(&self) -> usize {
90        if self.is_anonymous() { 4 } else { 3 }
91    }
92
93    /// Returns `true` if the event has more indexed parameters than allowed by
94    /// Solidity.
95    ///
96    /// See [`Self::max_indexed`].
97    #[inline]
98    pub fn exceeds_max_indexed(&self) -> bool {
99        self.indexed_params().count() > self.max_indexed()
100    }
101
102    /// Asserts that the event has a valid amount of indexed parameters.
103    pub fn assert_valid(&self) -> Result<()> {
104        if self.exceeds_max_indexed() {
105            let msg = if self.is_anonymous() {
106                "more than 4 indexed arguments for anonymous event"
107            } else {
108                "more than 3 indexed arguments for event"
109            };
110            Err(Error::new(self.span(), msg))
111        } else {
112            Ok(())
113        }
114    }
115
116    pub fn params(&self) -> ParameterList {
117        self.parameters.iter().map(EventParameter::as_param).collect()
118    }
119
120    pub fn param_types(
121        &self,
122    ) -> impl ExactSizeIterator<Item = &Type> + DoubleEndedIterator + Clone {
123        self.parameters.iter().map(|var| &var.ty)
124    }
125
126    pub fn param_types_mut(
127        &mut self,
128    ) -> impl ExactSizeIterator<Item = &mut Type> + DoubleEndedIterator {
129        self.parameters.iter_mut().map(|var| &mut var.ty)
130    }
131
132    pub fn param_types_and_names(
133        &self,
134    ) -> impl ExactSizeIterator<Item = (&Type, Option<&SolIdent>)> + DoubleEndedIterator {
135        self.parameters.iter().map(|p| (&p.ty, p.name.as_ref()))
136    }
137
138    pub fn param_type_strings(
139        &self,
140    ) -> impl ExactSizeIterator<Item = String> + DoubleEndedIterator + Clone + '_ {
141        self.parameters.iter().map(|var| var.ty.to_string())
142    }
143
144    pub fn non_indexed_params(&self) -> impl Iterator<Item = &EventParameter> {
145        self.parameters.iter().filter(|p| !p.is_indexed())
146    }
147
148    pub fn indexed_params(&self) -> impl Iterator<Item = &EventParameter> {
149        self.parameters.iter().filter(|p| p.is_indexed())
150    }
151
152    pub fn as_type(&self) -> Type {
153        let mut ty = Type::Tuple(self.parameters.iter().map(|arg| arg.ty.clone()).collect());
154        ty.set_span(self.span());
155        ty
156    }
157}
158
159/// An event parameter.
160///
161/// `<ty> [indexed] [name]`
162#[derive(Clone, PartialEq, Eq, Hash)]
163pub struct EventParameter {
164    pub attrs: Vec<Attribute>,
165    pub ty: Type,
166    pub indexed: Option<kw::indexed>,
167    pub name: Option<SolIdent>,
168}
169
170impl fmt::Display for EventParameter {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        self.ty.fmt(f)?;
173        if self.indexed.is_some() {
174            f.write_str(" indexed")?;
175        }
176        if let Some(name) = &self.name {
177            write!(f, " {name}")?;
178        }
179        Ok(())
180    }
181}
182
183impl fmt::Debug for EventParameter {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        f.debug_struct("EventParameter")
186            .field("attrs", &self.attrs)
187            .field("ty", &self.ty)
188            .field("indexed", &self.indexed.is_some())
189            .field("name", &self.name)
190            .finish()
191    }
192}
193
194impl Parse for EventParameter {
195    fn parse(input: ParseStream<'_>) -> Result<Self> {
196        Ok(Self {
197            attrs: input.call(Attribute::parse_outer)?,
198            ty: input.parse()?,
199            indexed: input.parse()?,
200            name: if SolIdent::peek_any(input) { Some(input.parse()?) } else { None },
201        })
202    }
203}
204
205impl Spanned for EventParameter {
206    /// Get the span of the event parameter
207    fn span(&self) -> Span {
208        let span = self.ty.span();
209        self.name.as_ref().and_then(|name| span.join(name.span())).unwrap_or(span)
210    }
211
212    /// Sets the span of the event parameter.
213    fn set_span(&mut self, span: Span) {
214        self.ty.set_span(span);
215        if let Some(kw) = &mut self.indexed {
216            kw.span = span;
217        }
218        if let Some(name) = &mut self.name {
219            name.set_span(span);
220        }
221    }
222}
223
224impl EventParameter {
225    /// Convert to a parameter declaration.
226    pub fn as_param(&self) -> VariableDeclaration {
227        VariableDeclaration {
228            attrs: self.attrs.clone(),
229            name: self.name.clone(),
230            storage: None,
231            ty: self.ty.clone(),
232        }
233    }
234
235    /// Returns `true` if the parameter is indexed.
236    #[inline]
237    pub const fn is_indexed(&self) -> bool {
238        self.indexed.is_some()
239    }
240
241    /// Returns `true` if the event parameter is stored in the event data
242    /// section.
243    pub const fn is_non_indexed(&self) -> bool {
244        self.indexed.is_none()
245    }
246
247    /// Returns `true` if the event parameter is indexed and dynamically sized.
248    /// These types are hashed, and then stored in the topics as specified in
249    /// [the Solidity spec][ref].
250    ///
251    /// `custom_is_value_type` accounts for custom value types.
252    ///
253    /// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#events
254    pub fn indexed_as_hash(&self, custom_is_value_type: impl Fn(&SolPath) -> bool) -> bool {
255        self.is_indexed() && !self.ty.is_value_type(custom_is_value_type)
256    }
257}