ink_ir/ir/trait_def/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 iter;
16mod trait_item;
17
18use self::iter::IterInkTraitItemsRaw;
19pub use self::{
20    iter::IterInkTraitItems,
21    trait_item::{
22        InkTraitItem,
23        InkTraitMessage,
24    },
25};
26use super::TraitDefinitionConfig;
27use crate::{
28    Selector,
29    ir,
30    ir::{
31        SelectorAbi,
32        TraitPrefix,
33        attrs::SelectorOrWildcard,
34        idents_lint,
35    },
36};
37use proc_macro2::{
38    Ident,
39    Span,
40};
41use std::collections::HashMap;
42use syn::{
43    Result,
44    spanned::Spanned as _,
45};
46
47/// A checked ink! trait definition without its configuration.
48#[derive(Debug, PartialEq, Eq)]
49pub struct InkItemTrait {
50    item: syn::ItemTrait,
51    message_selectors: HashMap<syn::Ident, Selector>,
52}
53
54#[cfg(test)]
55impl TryFrom<syn::ItemTrait> for InkItemTrait {
56    type Error = syn::Error;
57
58    fn try_from(item_trait: syn::ItemTrait) -> core::result::Result<Self, Self::Error> {
59        let config = TraitDefinitionConfig::default();
60        Self::new(&config, item_trait)
61    }
62}
63
64impl InkItemTrait {
65    /// Creates a new ink! item trait from the given configuration and trait definition.
66    pub fn new(
67        config: &TraitDefinitionConfig,
68        item_trait: syn::ItemTrait,
69    ) -> Result<Self> {
70        idents_lint::ensure_no_ink_identifiers(&item_trait)?;
71        Self::analyse_properties(&item_trait)?;
72        Self::analyse_items(&item_trait)?;
73        let mut message_selectors = <HashMap<syn::Ident, Selector>>::new();
74        Self::extract_selectors(config, &item_trait, &mut message_selectors)?;
75        if message_selectors.is_empty() {
76            return Err(format_err!(
77                item_trait.span(),
78                "encountered invalid empty ink! trait definition"
79            ))
80        }
81        Ok(Self {
82            item: item_trait,
83            message_selectors,
84        })
85    }
86}
87
88impl InkItemTrait {
89    /// Returns span of the ink! trait definition.
90    pub fn span(&self) -> Span {
91        self.item.span()
92    }
93
94    /// Returns the attributes of the ink! trait definition.
95    pub fn attrs(&self) -> &[syn::Attribute] {
96        &self.item.attrs
97    }
98
99    /// Returns the identifier of the ink! trait definition.
100    pub fn ident(&self) -> &Ident {
101        &self.item.ident
102    }
103
104    /// Returns an iterator yielding the ink! specific items of the ink! trait definition.
105    pub fn iter_items(&self) -> IterInkTraitItems<'_> {
106        IterInkTraitItems::new(self)
107    }
108
109    /// Analyses the properties of the ink! trait definition.
110    ///
111    /// # Errors
112    ///
113    /// - If the trait has been defined as `unsafe`.
114    /// - If the trait is an automatically implemented trait (`auto trait`).
115    /// - If the trait is generic over some set of types.
116    /// - If the trait's visibility is not public (`pub`).
117    fn analyse_properties(item_trait: &syn::ItemTrait) -> Result<()> {
118        if let Some(unsafety) = &item_trait.unsafety {
119            return Err(format_err_spanned!(
120                unsafety,
121                "ink! trait definitions cannot be unsafe"
122            ))
123        }
124        if let Some(auto) = &item_trait.auto_token {
125            return Err(format_err_spanned!(
126                auto,
127                "ink! trait definitions cannot be automatically implemented traits"
128            ))
129        }
130        if !item_trait.generics.params.is_empty() {
131            return Err(format_err_spanned!(
132                item_trait.generics.params,
133                "ink! trait definitions must not be generic"
134            ))
135        }
136        if !matches!(item_trait.vis, syn::Visibility::Public(_)) {
137            return Err(format_err_spanned!(
138                item_trait.vis,
139                "ink! trait definitions must have public visibility"
140            ))
141        }
142        if !item_trait.supertraits.is_empty() {
143            return Err(format_err_spanned!(
144                item_trait.supertraits,
145                "ink! trait definitions with supertraits are not supported, yet"
146            ))
147        }
148        Ok(())
149    }
150
151    /// Returns `Ok` if all trait items respects the requirements for an ink! trait
152    /// definition.
153    ///
154    /// # Errors
155    ///
156    /// - If the trait contains an unsupported trait item such as
157    ///     - associated constants (`const`)
158    ///     - associated types (`type`)
159    ///     - macros definitions or usages
160    ///     - unknown token sequences (verbatim)
161    ///     - methods with default implementations
162    /// - If the trait contains methods which do not respect the ink! trait definition
163    ///   requirements:
164    ///     - All trait methods need to be declared as either `#[ink(message)]` or
165    ///       `#[ink(constructor)]` and need to respect their respective rules.
166    ///
167    /// # Note
168    ///
169    /// Associated types and constants might be allowed in the future.
170    fn analyse_items(item_trait: &syn::ItemTrait) -> Result<()> {
171        for trait_item in &item_trait.items {
172            match trait_item {
173                syn::TraitItem::Const(const_trait_item) => {
174                    return Err(format_err_spanned!(
175                        const_trait_item,
176                        "associated constants in ink! trait definitions are not supported, yet"
177                    ))
178                }
179                syn::TraitItem::Macro(macro_trait_item) => {
180                    return Err(format_err_spanned!(
181                        macro_trait_item,
182                        "macros in ink! trait definitions are not supported"
183                    ))
184                }
185                syn::TraitItem::Type(type_trait_item) => {
186                    return Err(format_err_spanned!(
187                        type_trait_item,
188                        "associated types in ink! trait definitions are not supported, yet"
189                    ))
190                }
191                syn::TraitItem::Verbatim(verbatim) => {
192                    return Err(format_err_spanned!(
193                        verbatim,
194                        "encountered unsupported item in ink! trait definition"
195                    ))
196                }
197                syn::TraitItem::Fn(fn_trait_item) => {
198                    Self::analyse_trait_fn(fn_trait_item)?;
199                }
200                unknown => {
201                    return Err(format_err_spanned!(
202                        unknown,
203                        "encountered unknown or unsupported item in ink! trait definition"
204                    ))
205                }
206            }
207        }
208        Ok(())
209    }
210
211    /// Analyses an ink! method that can be either an ink! message or constructor.
212    ///
213    /// # Errors
214    ///
215    /// - If the method declared as `unsafe`, `const` or `async`.
216    /// - If the method has some explicit API.
217    /// - If the method is variadic or has generic parameters.
218    /// - If the method does not respect the properties of either an ink! message or ink!
219    ///   constructor.
220    fn analyse_trait_fn(method: &syn::TraitItemFn) -> Result<()> {
221        if let Some(default_impl) = &method.default {
222            return Err(format_err_spanned!(
223                default_impl,
224                "ink! trait methods with default implementations are not supported"
225            ))
226        }
227        if let Some(constness) = &method.sig.constness {
228            return Err(format_err_spanned!(
229                constness,
230                "const ink! trait methods are not supported"
231            ))
232        }
233        if let Some(asyncness) = &method.sig.asyncness {
234            return Err(format_err_spanned!(
235                asyncness,
236                "async ink! trait methods are not supported"
237            ))
238        }
239        if let Some(unsafety) = &method.sig.unsafety {
240            return Err(format_err_spanned!(
241                unsafety,
242                "unsafe ink! trait methods are not supported"
243            ))
244        }
245        if let Some(abi) = &method.sig.abi {
246            return Err(format_err_spanned!(
247                abi,
248                "ink! trait methods with non default ABI are not supported"
249            ))
250        }
251        if let Some(variadic) = &method.sig.variadic {
252            return Err(format_err_spanned!(
253                variadic,
254                "variadic ink! trait methods are not supported"
255            ))
256        }
257        if !method.sig.generics.params.is_empty() {
258            return Err(format_err_spanned!(
259                method.sig.generics.params,
260                "generic ink! trait methods are not supported"
261            ))
262        }
263        match ir::first_ink_attribute(&method.attrs) {
264            Ok(Some(ink_attr)) => {
265                match ink_attr.first().kind() {
266                    ir::AttributeArg::Message => {
267                        Self::analyse_trait_message(method)?;
268                    }
269                    ir::AttributeArg::Constructor => {
270                        Self::analyse_trait_constructor(method)?;
271                    }
272                    _unsupported => {
273                        return Err(format_err_spanned!(
274                            method,
275                            "encountered unsupported ink! attribute for ink! trait method",
276                        ))
277                    }
278                }
279            }
280            Ok(None) => {
281                return Err(format_err_spanned!(
282                    method,
283                    "missing #[ink(message)] or #[ink(constructor)] flags on ink! trait method"
284                ))
285            }
286            Err(err) => return Err(err),
287        }
288        Ok(())
289    }
290
291    /// Constructors are generally not allowed in ink! trait definitions.
292    fn analyse_trait_constructor(constructor: &syn::TraitItemFn) -> Result<()> {
293        Err(format_err!(
294            constructor.span(),
295            "ink! trait definitions must not have constructors",
296        ))
297    }
298
299    /// Analyses the properties of an ink! message.
300    ///
301    /// # Errors
302    ///
303    /// - If the message has no `&self` or `&mut self` receiver.
304    fn analyse_trait_message(message: &syn::TraitItemFn) -> Result<()> {
305        let (ink_attrs, _) =
306            InkTraitMessage::extract_attributes(message.span(), &message.attrs)?;
307        match message.sig.receiver() {
308            None => {
309                return Err(format_err_spanned!(
310                    message.sig,
311                    "missing `&self` or `&mut self` receiver for ink! message",
312                ))
313            }
314            Some(receiver) => {
315                if receiver.reference.is_none() {
316                    return Err(format_err_spanned!(
317                        receiver,
318                        "self receiver of ink! message must be `&self` or `&mut self`"
319                    ))
320                }
321
322                if ink_attrs.is_payable() && receiver.mutability.is_none() {
323                    return Err(format_err_spanned!(
324                        receiver,
325                        "ink! messages with a `payable` attribute argument must have a `&mut self` receiver"
326                    ))
327                }
328            }
329        }
330        Ok(())
331    }
332
333    /// Extract selectors for ink! trait constructors and messages.
334    ///
335    /// The composed or manually specified selectors are stored into the provided
336    /// hash tables for later look-up when querying ink! constructors or messages.
337    /// This way we are more flexible with regard to the underlying structures of the IR.
338    ///
339    /// In this step we assume that all sanitation checks have taken place prior so
340    /// instead of returning errors we simply panic upon failures.
341    ///
342    /// # Errors
343    ///
344    /// Returns an error if there are overlapping selectors for ink! constructors
345    /// or ink! messages. Note that overlaps between ink! constructor and message
346    /// selectors are allowed.
347    fn extract_selectors(
348        config: &TraitDefinitionConfig,
349        item_trait: &syn::ItemTrait,
350        message_selectors: &mut HashMap<syn::Ident, Selector>,
351    ) -> Result<()> {
352        let mut seen_message_selectors = <HashMap<Selector, syn::Ident>>::new();
353        let (_ink_attrs, _) = ir::sanitize_optional_attributes(
354            item_trait.span(),
355            item_trait.attrs.iter().cloned(),
356            |arg| {
357                match arg.kind() {
358                    ir::AttributeArg::Namespace(_) => Ok(()),
359                    _ => Err(None),
360                }
361            },
362        )
363        .unwrap_or_else(|err| {
364            panic!("encountered unexpected invalid attributes on ink! trait definition: {err}")
365        });
366        let namespace = config.namespace();
367        let ident = &item_trait.ident;
368        let trait_prefix = TraitPrefix::new(ident, namespace);
369        for callable in IterInkTraitItemsRaw::from_raw(item_trait) {
370            let ident = callable.ident();
371            let ink_attrs = callable.ink_attrs();
372            let selector = match ink_attrs.selector() {
373                Some(SelectorOrWildcard::UserProvided(manual_selector)) => {
374                    manual_selector
375                }
376                _ => {
377                    let name = callable.normalized_name();
378                    Selector::compose(trait_prefix, name, SelectorAbi::Ink)
379                }
380            };
381            let (duplicate_selector, duplicate_ident) = match callable {
382                InkTraitItem::Message(_) => {
383                    let duplicate_selector =
384                        seen_message_selectors.insert(selector, ident.clone());
385                    let duplicate_ident =
386                        message_selectors.insert(ident.clone(), selector);
387                    (duplicate_selector, duplicate_ident)
388                }
389            };
390            if let Some(duplicate_selector) = duplicate_selector {
391                use crate::error::ExtError as _;
392                return Err(format_err_spanned!(
393                    ident,
394                    "encountered duplicate selector ({:x?}) in the same ink! trait definition",
395                    selector.to_bytes(),
396                ).into_combine(format_err_spanned!(
397                    duplicate_selector,
398                    "first ink! trait constructor or message with same selector found here",
399                )));
400            }
401            assert!(
402                duplicate_ident.is_none(),
403                "encountered unexpected overlapping ink! trait constructor or message identifier",
404            );
405        }
406        Ok(())
407    }
408}