syn_solidity/item/
contract.rs

1use crate::{Item, Modifier, SolIdent, Spanned, Type, kw, utils::DebugPunctuated};
2use proc_macro2::Span;
3use std::{cmp::Ordering, fmt};
4use syn::{
5    Attribute, Error, Result, Token, braced,
6    parse::{Lookahead1, Parse, ParseStream},
7    punctuated::Punctuated,
8    token::Brace,
9};
10
11/// A contract, abstract contract, interface, or library definition:
12/// `contract Foo is Bar("foo"), Baz { ... }`.
13///
14/// Solidity reference:
15/// <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.contractDefinition>
16#[derive(Clone)]
17pub struct ItemContract {
18    pub attrs: Vec<Attribute>,
19    pub kind: ContractKind,
20    pub name: SolIdent,
21    pub inheritance: Option<Inheritance>,
22    pub brace_token: Brace,
23    pub body: Vec<Item>,
24}
25
26impl fmt::Display for ItemContract {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "{} {}", self.kind, self.name)?;
29        if let Some(inheritance) = &self.inheritance {
30            write!(f, " {inheritance}")?;
31        }
32        let s = self
33            .body
34            .iter()
35            .map(|item| item.to_string())
36            .collect::<Vec<_>>()
37            .join("\n")
38            .replace('\n', "\n    ");
39        write!(f, " {{\n    {s}\n}}")
40    }
41}
42
43impl fmt::Debug for ItemContract {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.debug_struct("ItemContract")
46            .field("attrs", &self.attrs)
47            .field("kind", &self.kind)
48            .field("name", &self.name)
49            .field("inheritance", &self.inheritance)
50            .field("body", &self.body)
51            .finish()
52    }
53}
54
55impl Parse for ItemContract {
56    fn parse(input: ParseStream<'_>) -> Result<Self> {
57        let kind;
58        let content;
59        Ok(Self {
60            attrs: input.call(Attribute::parse_outer)?,
61            kind: {
62                kind = input.parse()?;
63                kind
64            },
65            name: input.parse()?,
66            inheritance: {
67                if input.peek(kw::is) {
68                    if kind.is_library() {
69                        return Err(input.error("libraries are not allowed to inherit"));
70                    }
71                    Some(input.parse()?)
72                } else {
73                    None
74                }
75            },
76            brace_token: braced!(content in input),
77            body: {
78                let mut body = Vec::new();
79                while !content.is_empty() {
80                    let item: Item = content.parse()?;
81                    if matches!(item, Item::Contract(_)) {
82                        return Err(Error::new(item.span(), "cannot declare nested contracts"));
83                    }
84                    body.push(item);
85                }
86                body
87            },
88        })
89    }
90}
91
92impl Spanned for ItemContract {
93    fn span(&self) -> Span {
94        self.name.span()
95    }
96
97    fn set_span(&mut self, span: Span) {
98        self.name.set_span(span);
99    }
100}
101
102impl ItemContract {
103    pub fn as_type(&self) -> Type {
104        Type::Address(self.span(), None)
105    }
106
107    /// Returns true if `self` is an abstract contract.
108    pub fn is_abstract_contract(&self) -> bool {
109        self.kind.is_abstract_contract()
110    }
111
112    /// Returns true if `self` is a contract.
113    pub fn is_contract(&self) -> bool {
114        self.kind.is_contract()
115    }
116
117    /// Returns true if `self` is an interface.
118    pub fn is_interface(&self) -> bool {
119        self.kind.is_interface()
120    }
121
122    /// Returns true if `self` is a library.
123    pub fn is_library(&self) -> bool {
124        self.kind.is_library()
125    }
126}
127
128/// The kind of contract.
129#[derive(Clone, Copy, PartialEq, Eq, Hash)]
130pub enum ContractKind {
131    /// `abstract contract`
132    AbstractContract(Token![abstract], kw::contract),
133    /// `contract`
134    Contract(kw::contract),
135    /// `interface`
136    Interface(kw::interface),
137    /// `library`
138    Library(kw::library),
139}
140
141impl fmt::Display for ContractKind {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.write_str(self.as_str())
144    }
145}
146
147impl fmt::Debug for ContractKind {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        f.write_str(self.as_debug_str())
150    }
151}
152
153impl PartialOrd for ContractKind {
154    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
155        Some(self.cmp(other))
156    }
157}
158
159impl Ord for ContractKind {
160    fn cmp(&self, other: &Self) -> Ordering {
161        self.idx().cmp(&other.idx())
162    }
163}
164
165impl Parse for ContractKind {
166    fn parse(input: ParseStream<'_>) -> Result<Self> {
167        let lookahead = input.lookahead1();
168        if lookahead.peek(Token![abstract]) {
169            Ok(Self::AbstractContract(input.parse()?, input.parse()?))
170        } else if lookahead.peek(kw::contract) {
171            input.parse().map(Self::Contract)
172        } else if lookahead.peek(kw::interface) {
173            input.parse().map(Self::Interface)
174        } else if lookahead.peek(kw::library) {
175            input.parse().map(Self::Library)
176        } else {
177            Err(lookahead.error())
178        }
179    }
180}
181
182impl Spanned for ContractKind {
183    fn span(&self) -> Span {
184        match self {
185            Self::AbstractContract(kw_abstract, kw_contract) => {
186                let span = kw_abstract.span;
187                span.join(kw_contract.span).unwrap_or(span)
188            }
189            Self::Contract(kw) => kw.span,
190            Self::Interface(kw) => kw.span,
191            Self::Library(kw) => kw.span,
192        }
193    }
194
195    fn set_span(&mut self, span: Span) {
196        match self {
197            Self::AbstractContract(kw_abstract, kw_contract) => {
198                kw_abstract.span = span;
199                kw_contract.span = span;
200            }
201            Self::Contract(kw) => kw.span = span,
202            Self::Interface(kw) => kw.span = span,
203            Self::Library(kw) => kw.span = span,
204        }
205    }
206}
207
208impl ContractKind {
209    pub fn peek(lookahead: &Lookahead1<'_>) -> bool {
210        lookahead.peek(Token![abstract])
211            || lookahead.peek(kw::contract)
212            || lookahead.peek(kw::interface)
213            || lookahead.peek(kw::library)
214    }
215
216    /// Returns true if `self` is an abstract contract.
217    pub fn is_abstract_contract(self) -> bool {
218        matches!(self, Self::AbstractContract(..))
219    }
220
221    /// Returns true if `self` is a contract.
222    pub fn is_contract(self) -> bool {
223        matches!(self, Self::Contract(_))
224    }
225
226    /// Returns true if `self` is an interface.
227    pub fn is_interface(self) -> bool {
228        matches!(self, Self::Interface(_))
229    }
230
231    /// Returns true if `self` is a library.
232    pub fn is_library(self) -> bool {
233        matches!(self, Self::Library(_))
234    }
235
236    pub const fn as_debug_str(self) -> &'static str {
237        match self {
238            Self::AbstractContract(..) => "AbstractContract",
239            Self::Contract(_) => "Contract",
240            Self::Interface(_) => "Interface",
241            Self::Library(_) => "Library",
242        }
243    }
244
245    pub const fn as_str(self) -> &'static str {
246        match self {
247            Self::AbstractContract(..) => "abstract contract",
248            Self::Contract(_) => "contract",
249            Self::Interface(_) => "interface",
250            Self::Library(_) => "library",
251        }
252    }
253
254    fn idx(&self) -> usize {
255        match self {
256            Self::AbstractContract(..) => 0,
257            Self::Contract(_) => 1,
258            Self::Interface(_) => 2,
259            Self::Library(_) => 3,
260        }
261    }
262}
263
264/// A list of inheritance specifiers of an [`ItemContract`]:
265/// `is ERC20("Token", "TKN"), Ownable`.
266///
267/// Solidity reference:
268/// <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.inheritanceSpecifier>
269#[derive(Clone, PartialEq, Eq, Hash)]
270pub struct Inheritance {
271    pub is_token: kw::is,
272    pub inheritance: Punctuated<Modifier, Token![,]>,
273}
274
275impl fmt::Display for Inheritance {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        f.write_str("is ")?;
278        for (i, modifier) in self.inheritance.iter().enumerate() {
279            if i > 0 {
280                f.write_str(", ")?;
281            }
282            modifier.fmt(f)?;
283        }
284        Ok(())
285    }
286}
287
288impl fmt::Debug for Inheritance {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        f.debug_tuple("Inheritance").field(DebugPunctuated::new(&self.inheritance)).finish()
291    }
292}
293
294impl Parse for Inheritance {
295    fn parse(input: ParseStream<'_>) -> Result<Self> {
296        let is_token = input.parse()?;
297        let mut inheritance = Punctuated::new();
298        loop {
299            if input.is_empty() || input.peek(Brace) {
300                break;
301            }
302            inheritance.push_value(input.parse()?);
303            if input.is_empty() || input.peek(Brace) {
304                break;
305            }
306            inheritance.push_punct(input.parse()?);
307        }
308        if inheritance.is_empty() {
309            Err(input.parse::<SolIdent>().unwrap_err())
310        } else {
311            Ok(Self { is_token, inheritance })
312        }
313    }
314}
315
316impl Spanned for Inheritance {
317    fn span(&self) -> Span {
318        let span = self.is_token.span;
319        self.inheritance.last().and_then(|last| span.join(last.span())).unwrap_or(span)
320    }
321
322    fn set_span(&mut self, span: Span) {
323        self.is_token.span = span;
324    }
325}