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