syn_solidity/item/
event.rs

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