ink_lang_ir/ir/item_impl/impl_item.rs
1// Copyright 2018-2022 Parity Technologies (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
15use super::{
16    Constructor,
17    Message,
18};
19use crate::{
20    error::ExtError as _,
21    ir,
22    ir::attrs::Attrs as _,
23};
24use syn::spanned::Spanned as _;
25
26/// An item within an ink! implementation block.
27///
28/// Can be either
29/// - an ink! [`ir::Constructor`](`crate::ir::Constructor`)
30/// - an ink! [`ir::Message`](`crate::ir::Message`)
31/// - or any other non-ink! item.
32///
33/// # Note
34///
35/// Based on [`syn::ImplItem`] with special variants for ink! `impl` items.
36#[derive(Debug, PartialEq, Eq)]
37#[allow(clippy::large_enum_variant)]
38pub enum ImplItem {
39    /// A `#[ink(constructor)]` marked inherent function.
40    Constructor(Constructor),
41    /// A `#[ink(message)]` marked method.
42    Message(Message),
43    /// Any other implementation block item.
44    Other(syn::ImplItem),
45}
46
47impl quote::ToTokens for ImplItem {
48    /// We mainly implement this trait for this ink! type to have a derived
49    /// [`Spanned`](`syn::spanned::Spanned`) implementation for it.
50    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
51        match self {
52            Self::Constructor(constructor) => constructor.to_tokens(tokens),
53            Self::Message(message) => message.to_tokens(tokens),
54            Self::Other(other) => other.to_tokens(tokens),
55        }
56    }
57}
58
59impl TryFrom<syn::ImplItem> for ImplItem {
60    type Error = syn::Error;
61
62    fn try_from(impl_item: syn::ImplItem) -> Result<Self, Self::Error> {
63        match impl_item {
64            syn::ImplItem::Method(method_item) => {
65                if !ir::contains_ink_attributes(&method_item.attrs) {
66                    return Ok(Self::Other(method_item.into()))
67                }
68                let attr = ir::first_ink_attribute(&method_item.attrs)?
69                    .expect("missing expected ink! attribute for struct");
70                match attr.first().kind() {
71                    ir::AttributeArg::Message => {
72                        <Message as TryFrom<_>>::try_from(method_item)
73                            .map(Into::into)
74                            .map(Self::Message)
75                    }
76                    ir::AttributeArg::Constructor => {
77                        <Constructor as TryFrom<_>>::try_from(method_item)
78                            .map(Into::into)
79                            .map(Self::Constructor)
80                    }
81                    _ => Err(format_err_spanned!(
82                        method_item,
83                        "encountered invalid ink! attribute at this point, expected either \
84                        #[ink(message)] or #[ink(constructor) attributes"
85                    )),
86                }
87            }
88            other_item => {
89                // This is an error if the `impl` item contains any unexpected
90                // ink! attributes. Otherwise it is a normal Rust item.
91                if ir::contains_ink_attributes(other_item.attrs()) {
92                    let (ink_attrs, _) =
93                        ir::partition_attributes(other_item.attrs().iter().cloned())?;
94                    assert!(!ink_attrs.is_empty());
95                    fn into_err(attr: &ir::InkAttribute) -> syn::Error {
96                        format_err!(attr.span(), "encountered unexpected ink! attribute",)
97                    }
98                    return Err(ink_attrs[1..]
99                        .iter()
100                        .map(into_err)
101                        .fold(into_err(&ink_attrs[0]), |fst, snd| fst.into_combine(snd)))
102                }
103                Ok(Self::Other(other_item))
104            }
105        }
106    }
107}
108
109impl ImplItem {
110    /// Returns `true` if the `impl` block item is an ink! message.
111    pub fn is_message(&self) -> bool {
112        self.filter_map_message().is_some()
113    }
114
115    /// Returns `Some` if `self` is an ink! message.
116    ///
117    /// Otherwise, returns `None`.
118    pub fn filter_map_message(&self) -> Option<&Message> {
119        match self {
120            ImplItem::Message(message) => Some(message),
121            _ => None,
122        }
123    }
124
125    /// Returns `true` if the `impl` block item is an ink! message.
126    pub fn is_constructor(&self) -> bool {
127        self.filter_map_constructor().is_some()
128    }
129
130    /// Returns `Some` if `self` is an ink! constructor.
131    ///
132    /// Otherwise, returns `None`.
133    pub fn filter_map_constructor(&self) -> Option<&Constructor> {
134        match self {
135            ImplItem::Constructor(constructor) => Some(constructor),
136            _ => None,
137        }
138    }
139
140    /// Returns `true` if the `impl` block item is a non ink! specific item.
141    pub fn is_other_item(&self) -> bool {
142        self.filter_map_other_item().is_some()
143    }
144
145    /// Returns `Some` if `self` is a not an ink! specific item.
146    ///
147    /// Otherwise, returns `None`.
148    pub fn filter_map_other_item(&self) -> Option<&syn::ImplItem> {
149        match self {
150            ImplItem::Other(rust_item) => Some(rust_item),
151            _ => None,
152        }
153    }
154}