ink_ir/ir/item/
mod.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod storage;
16
17#[cfg(test)]
18mod tests;
19
20pub use self::storage::Storage;
21
22use crate::{
23    error::ExtError as _,
24    ir,
25    ir::attrs::Attrs as _,
26};
27use syn::spanned::Spanned as _;
28
29/// An item in the root of the ink! module ([`ir::ItemMod`](`crate::ir::ItemMod`)).
30///
31/// This is either an ink! specific item or a normal Rust item.
32#[derive(Debug, PartialEq, Eq)]
33pub enum Item {
34    /// The item is an ink! specific item.
35    Ink(InkItem),
36    /// The item is a normal Rust item.
37    Rust(syn::Item),
38}
39
40impl quote::ToTokens for Item {
41    /// We mainly implement this trait for this ink! type to have a derived
42    /// [`Spanned`](`syn::spanned::Spanned`) implementation for it.
43    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
44        match self {
45            Self::Ink(ink_item) => ink_item.to_tokens(tokens),
46            Self::Rust(rust_item) => rust_item.to_tokens(tokens),
47        }
48    }
49}
50
51impl TryFrom<syn::Item> for Item {
52    type Error = syn::Error;
53
54    fn try_from(item: syn::Item) -> Result<Self, Self::Error> {
55        match item {
56            syn::Item::Struct(item_struct) => {
57                if !ir::contains_ink_attributes(&item_struct.attrs) {
58                    return Ok(Self::Rust(item_struct.into()))
59                }
60                // At this point we know that there must be at least one ink!
61                // attribute. This can be either the ink! storage struct,
62                // an ink! event or an invalid ink! attribute.
63                let attr = ir::first_ink_attribute(&item_struct.attrs)?
64                    .expect("missing expected ink! attribute for struct");
65                match attr.first().kind() {
66                    ir::AttributeArg::Storage => {
67                        <ir::Storage as TryFrom<_>>::try_from(item_struct)
68                            .map(Into::into)
69                            .map(Self::Ink)
70                    }
71                    ir::AttributeArg::Event => {
72                        <ir::Event as TryFrom<_>>::try_from(item_struct)
73                            .map(Into::into)
74                            .map(Self::Ink)
75                    }
76                    _invalid => {
77                        Err(format_err!(
78                            attr.span(),
79                            "encountered unsupported ink! attribute argument on struct",
80                        ))
81                    }
82                }
83            }
84            syn::Item::Impl(item_impl) => {
85                if !ir::ItemImpl::is_ink_impl_block(&item_impl)? {
86                    return Ok(Self::Rust(item_impl.into()))
87                }
88                // At this point we know that there must be at least one ink!
89                // attribute on either the `impl` block itself or one of its items.
90                <ir::ItemImpl as TryFrom<_>>::try_from(item_impl)
91                    .map(Into::into)
92                    .map(Self::Ink)
93            }
94            item => {
95                // This is an error if the item contains any unexpected
96                // ink! attributes. Otherwise it is a normal Rust item.
97                if ir::contains_ink_attributes(item.attrs()) {
98                    let (ink_attrs, _) =
99                        ir::partition_attributes(item.attrs().iter().cloned())?;
100                    assert!(!ink_attrs.is_empty());
101                    fn into_err(attr: &ir::InkAttribute) -> syn::Error {
102                        format_err!(attr.span(), "encountered unexpected ink! attribute",)
103                    }
104                    return Err(ink_attrs[1..]
105                        .iter()
106                        .map(into_err)
107                        .fold(into_err(&ink_attrs[0]), |fst, snd| fst.into_combine(snd)))
108                }
109                Ok(Self::Rust(item))
110            }
111        }
112    }
113}
114
115impl Item {
116    /// Returns `true` if `self` is an ink! specific item.
117    pub fn is_ink_item(&self) -> bool {
118        self.map_ink_item().is_some()
119    }
120
121    /// Returns `true` if `self` is a normal Rust item.
122    pub fn is_rust_item(&self) -> bool {
123        self.map_rust_item().is_some()
124    }
125
126    /// Returns `Some` if `self` is an ink! specific item.
127    ///
128    /// Otherwise, returns `None`.
129    pub fn map_ink_item(&self) -> Option<&InkItem> {
130        match self {
131            Item::Ink(ink_item) => Some(ink_item),
132            _ => None,
133        }
134    }
135
136    /// Returns `Some` if `self` is an ink! specific item.
137    ///
138    /// Otherwise, returns `None`.
139    pub fn map_rust_item(&self) -> Option<&syn::Item> {
140        match self {
141            Item::Rust(rust_item) => Some(rust_item),
142            _ => None,
143        }
144    }
145}
146
147/// An ink! specific item.
148#[derive(Debug, PartialEq, Eq)]
149pub enum InkItem {
150    /// The ink! storage struct definition.
151    Storage(ir::Storage),
152    /// An ink! event definition.
153    Event(ir::Event),
154    /// An ink! implementation block.
155    ImplBlock(ir::ItemImpl),
156}
157
158impl quote::ToTokens for InkItem {
159    /// We mainly implement this trait for this ink! type to have a derived
160    /// [`Spanned`](`syn::spanned::Spanned`) implementation for it.
161    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
162        match self {
163            Self::Storage(storage) => storage.to_tokens(tokens),
164            Self::Event(event) => event.to_tokens(tokens),
165            Self::ImplBlock(impl_block) => impl_block.to_tokens(tokens),
166        }
167    }
168}
169
170impl InkItem {
171    /// Returns `true` if the given [`syn::Item`] is eventually an ink! item.
172    ///
173    /// # Errors
174    ///
175    /// If invalid or malformed ink! attributes are encountered for the given item.
176    pub fn is_ink_item(item: &syn::Item) -> Result<bool, syn::Error> {
177        match item {
178            syn::Item::Struct(item_struct) => {
179                if ir::Storage::is_ink_storage(item_struct)?
180                    || ir::Event::is_ink_event(item_struct)?
181                {
182                    return Ok(true)
183                }
184            }
185            syn::Item::Impl(item_impl) => {
186                return ir::ItemImpl::is_ink_impl_block(item_impl)
187            }
188            _ => (),
189        }
190        Ok(false)
191    }
192}
193
194impl From<ir::Storage> for InkItem {
195    fn from(storage: ir::Storage) -> Self {
196        Self::Storage(storage)
197    }
198}
199
200impl From<ir::Event> for InkItem {
201    fn from(event: ir::Event) -> Self {
202        Self::Event(event)
203    }
204}
205
206impl From<ir::ItemImpl> for InkItem {
207    fn from(impl_block: ir::ItemImpl) -> Self {
208        Self::ImplBlock(impl_block)
209    }
210}
211
212impl InkItem {
213    /// Returns `Some` if `self` is the ink! storage struct definition.
214    ///
215    /// Otherwise, returns `None`.
216    pub fn filter_map_storage_item(&self) -> Option<&ir::Storage> {
217        match self {
218            InkItem::Storage(storage) => Some(storage),
219            _ => None,
220        }
221    }
222
223    /// Returns `true` if the ink! specific item is the storage struct definition.
224    pub fn is_storage_item(&self) -> bool {
225        self.filter_map_storage_item().is_some()
226    }
227
228    /// Returns `Some` if `self` is an ink! event struct definition.
229    ///
230    /// Otherwise, returns `None`.
231    pub fn filter_map_event_item(&self) -> Option<&ir::Event> {
232        match self {
233            InkItem::Event(event) => Some(event),
234            _ => None,
235        }
236    }
237
238    /// Returns `true` if the ink! specific item is an event struct definition.
239    pub fn is_event_item(&self) -> bool {
240        self.filter_map_event_item().is_some()
241    }
242
243    /// Returns `Some` if `self` is an ink! implementation block.
244    ///
245    /// Otherwise, returns `None`.
246    pub fn filter_map_impl_block(&self) -> Option<&ir::ItemImpl> {
247        match self {
248            InkItem::ImplBlock(impl_block) => Some(impl_block),
249            _ => None,
250        }
251    }
252
253    /// Returns `true` if the ink! specific item is an implementation block.
254    pub fn is_impl_block(&self) -> bool {
255        self.filter_map_impl_block().is_some()
256    }
257}