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
15use crate::{
16    Callable,
17    error::ExtError as _,
18    ir,
19    ir::idents_lint,
20};
21use proc_macro2::{
22    Ident,
23    Span,
24};
25use quote::TokenStreamExt as _;
26use std::collections::HashMap;
27use syn::{
28    spanned::Spanned,
29    token,
30};
31
32/// The ink! module.
33///
34/// This is the root of all ink! smart contracts and is defined similarly to
35/// a normal Rust module annotated with
36/// `#[ink::contract( /* optional configuration */ )]` attribute.
37///
38/// It contains ink! specific items as well as normal Rust items.
39///
40/// # Example
41///
42/// ```
43/// // #[ink::contract] <-- this line belongs to the ink! configuration!
44/// # use ink_ir as ir;
45/// # <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
46/// mod my_contract {
47///     #[ink(storage)]
48///     pub struct MyStorage {
49///         // storage fields
50///     }
51///
52///     #[ink(event)]
53///     pub struct MyEvent {
54///         // event fields
55///     }
56///
57///     impl MyStorage {
58///         #[ink(constructor)]
59///         pub fn my_constructor() -> Self {
60///             // constructor initialization
61///         }
62///
63///         #[ink(message)]
64///         pub fn my_message(&self) {
65///             // message statements
66///         }
67///     }
68/// }
69/// # }).unwrap();
70/// ```
71///
72/// # Note
73///
74/// This type has been named after [`syn::ItemMod`] and inherits all of the
75/// fields that are required for inline module definitions.
76///
77/// # Developer Note
78///
79/// Structurally the ink! `Module` mirrors an inline Rust module, for example:
80///
81/// ```
82/// mod rust_module {
83///     // some Rust item definitions
84/// }
85/// ```
86///
87/// If the capabilities of an inline Rust module change we have to adjust for that.
88#[derive(Debug, PartialEq, Eq)]
89pub struct ItemMod {
90    attrs: Vec<syn::Attribute>,
91    vis: syn::Visibility,
92    mod_token: token::Mod,
93    ident: Ident,
94    brace: token::Brace,
95    items: Vec<ir::Item>,
96}
97
98impl ItemMod {
99    /// Ensures that the ink! storage struct is not missing and that there are
100    /// not multiple ink! storage struct definitions for the given slice of items.
101    fn ensure_storage_struct_quantity(
102        module_span: Span,
103        items: &[ir::Item],
104    ) -> Result<(), syn::Error> {
105        let storage_iter = items
106            .iter()
107            .filter(|item| matches!(item, ir::Item::Ink(ir::InkItem::Storage(_))));
108        if storage_iter.clone().next().is_none() {
109            return Err(format_err!(module_span, "missing ink! storage struct",))
110        }
111        if storage_iter.clone().count() >= 2 {
112            let mut error = format_err!(
113                module_span,
114                "encountered multiple ink! storage structs, expected exactly one"
115            );
116            for storage in storage_iter {
117                error.combine(format_err!(storage, "ink! storage struct here"))
118            }
119            return Err(error)
120        }
121        Ok(())
122    }
123
124    /// Ensures that the given slice of items contains at least one ink! message.
125    fn ensure_contains_message(
126        module_span: Span,
127        items: &[ir::Item],
128    ) -> Result<(), syn::Error> {
129        let found_message = items
130            .iter()
131            .filter_map(|item| {
132                match item {
133                    ir::Item::Ink(ir::InkItem::ImplBlock(item_impl)) => {
134                        Some(item_impl.iter_messages())
135                    }
136                    _ => None,
137                }
138            })
139            .any(|mut messages| messages.next().is_some());
140        if !found_message {
141            return Err(format_err!(module_span, "missing ink! message"))
142        }
143        Ok(())
144    }
145
146    /// Ensures that the given slice of items contains at least one ink! constructor.
147    ///
148    /// # Note
149    ///
150    /// Also ensure that there's only one constructor in "sol" ABI mode,
151    /// or that at least one constructor is annotated as the default constructor in "all"
152    /// ABI mode.
153    ///
154    /// See <https://use.ink/docs/v6/basics/abi> for details about ABI modes.
155    fn ensure_contains_constructor(
156        module_span: Span,
157        items: &[ir::Item],
158    ) -> Result<(), syn::Error> {
159        let all_constructors = || {
160            items
161                .iter()
162                .filter_map(|item| {
163                    match item {
164                        ir::Item::Ink(ir::InkItem::ImplBlock(item_impl)) => {
165                            Some(item_impl.iter_constructors())
166                        }
167                        _ => None,
168                    }
169                })
170                .flatten()
171        };
172
173        let n_constructors = all_constructors().count();
174        if n_constructors == 0 {
175            return Err(format_err!(module_span, "missing ink! constructor"));
176        }
177
178        #[cfg(ink_abi = "sol")]
179        if n_constructors > 1 {
180            return Err(format_err!(
181                module_span,
182                "multiple constructors are not supported in Solidity ABI compatibility mode"
183            ));
184        }
185
186        #[cfg(ink_abi = "all")]
187        {
188            let has_default_constructor =
189                || all_constructors().any(|constructor| constructor.is_default());
190            if n_constructors > 1 && !has_default_constructor() {
191                return Err(format_err!(
192                    module_span,
193                    "One constructor used for Solidity ABI encoded instantiation \
194                    must be annotated with the `default` attribute argument \
195                    in \"all\" ABI mode"
196                ));
197            }
198        }
199
200        Ok(())
201    }
202
203    /// Ensures that no ink! message or constructor selectors are overlapping.
204    ///
205    /// # Note
206    ///
207    /// We differentiate between ink! message and ink! constructor selectors
208    /// since they are dispatched independently from each other and thus are
209    /// allowed to have overlapping selectors.
210    fn ensure_no_overlapping_selectors(items: &[ir::Item]) -> Result<(), syn::Error> {
211        let mut messages = <HashMap<ir::Selector, &ir::Message>>::new();
212        let mut constructors = <HashMap<ir::Selector, &ir::Constructor>>::new();
213        for item_impl in items
214            .iter()
215            .filter_map(ir::Item::map_ink_item)
216            .filter_map(ir::InkItem::filter_map_impl_block)
217        {
218            use std::collections::hash_map::Entry;
219            /// Kind is either `"message"` or `"constructor"`.
220            fn compose_error(
221                first_span: Span,
222                second_span: Span,
223                selector: ir::Selector,
224                kind: &str,
225            ) -> syn::Error {
226                format_err!(
227                    second_span,
228                    "encountered ink! {}s with overlapping selectors (= {:02X?})\n\
229                     hint: use #[ink(selector = S:u32)] on the callable or \
230                     #[ink(namespace = N:string)] on the implementation block to \
231                     disambiguate overlapping selectors.",
232                    kind,
233                    selector.to_bytes(),
234                )
235                .into_combine(format_err!(
236                    first_span,
237                    "first ink! {} with overlapping selector here",
238                    kind,
239                ))
240            }
241            for message in item_impl.iter_messages() {
242                let selector = message.composed_selector();
243                match messages.entry(selector) {
244                    Entry::Occupied(overlap) => {
245                        return Err(compose_error(
246                            overlap.get().span(),
247                            message.callable().span(),
248                            selector,
249                            "message",
250                        ))
251                    }
252                    Entry::Vacant(vacant) => {
253                        vacant.insert(message.callable());
254                    }
255                }
256            }
257            for constructor in item_impl.iter_constructors() {
258                let selector = constructor.composed_selector();
259                match constructors.entry(selector) {
260                    Entry::Occupied(overlap) => {
261                        return Err(compose_error(
262                            overlap.get().span(),
263                            constructor.callable().span(),
264                            selector,
265                            "constructor",
266                        ))
267                    }
268                    Entry::Vacant(vacant) => {
269                        vacant.insert(constructor.callable());
270                    }
271                }
272            }
273        }
274        Ok(())
275    }
276
277    /// Ensures that the `#[cfg(…)]` contains only valid attributes.
278    ///
279    /// # Note
280    ///
281    /// This restriction was added to prevent contract developers from
282    /// adding public constructors/messages that don't show up in the
283    /// ink! metadata, but are compiled into the contract binary.
284    ///
285    /// Or formulated differently: we allow only `#[cfg(…)]`'s that don't
286    /// allow differentiating between compiling for PolkaVM vs. native.
287    ///
288    /// Without this restriction users that view the metadata can be
289    /// deceived as to what functions the contract provides to the public.
290    fn ensure_only_allowed_cfgs(items: &[ir::Item]) -> Result<(), syn::Error> {
291        const ERR_HELP: &str = "Allowed in `#[cfg(…)]`:\n\
292               - `test`\n\
293               - `feature` (without `std`)\n\
294               - `any`\n\
295               - `not`\n\
296               - `all`";
297
298        fn verify_attr(a: &syn::Attribute) -> Result<(), syn::Error> {
299            match &a.meta {
300                syn::Meta::List(list) => {
301                    if let Some(ident) = list.path.get_ident()
302                        && ident.eq("cfg")
303                    {
304                        return list.parse_nested_meta(verify_cfg_attrs);
305                    }
306                    unreachable!(
307                        "`verify_attr` can only be called for `#[cfg(…)]`, not for other `List`"
308                    );
309                }
310                syn::Meta::Path(_) => {
311                    // not relevant, we are only looking at `#[cfg(…)]`
312                    unreachable!(
313                        "`verify_attr` can only be called for `#[cfg(…)]`, not for `Path`"
314                    );
315                }
316                syn::Meta::NameValue(_) => {
317                    // not relevant, we are only looking at `#[cfg(…)]`
318                    unreachable!(
319                        "`verify_attr` can only be called for `#[cfg(…)]`, not for `NameValue`"
320                    );
321                }
322            }
323        }
324
325        fn verify_cfg_attrs(meta: syn::meta::ParseNestedMeta) -> Result<(), syn::Error> {
326            if meta.path.is_ident("test") {
327                return Ok(());
328            }
329            if meta.path.is_ident("any")
330                || meta.path.is_ident("all")
331                || meta.path.is_ident("not")
332            {
333                return meta.parse_nested_meta(verify_cfg_attrs);
334            }
335
336            if meta.path.is_ident("feature") {
337                let value = meta.value()?;
338                let value: syn::LitStr = value.parse()?;
339                if value.value().eq("std") {
340                    return Err(format_err_spanned!(
341                        meta.path,
342                        "The feature `std` is not allowed in `cfg`.\n\n{ERR_HELP}"
343                    ))
344                }
345                return Ok(());
346            }
347
348            Err(format_err_spanned!(
349                meta.path,
350                "This `cfg` attribute is not allowed.\n\n{ERR_HELP}"
351            ))
352        }
353
354        for item_impl in items
355            .iter()
356            .filter_map(ir::Item::map_ink_item)
357            .filter_map(ir::InkItem::filter_map_impl_block)
358        {
359            for message in item_impl.iter_messages() {
360                for a in message.get_cfg_syn_attrs() {
361                    verify_attr(&a)?;
362                }
363            }
364            for constructor in item_impl.iter_constructors() {
365                for a in constructor.get_cfg_syn_attrs() {
366                    verify_attr(&a)?;
367                }
368            }
369        }
370        Ok(())
371    }
372
373    /// Ensures that:
374    /// - At most one wildcard selector exists among ink! messages, as well as ink!
375    ///   constructors.
376    /// - Where a wildcard selector is defined for a message, at most one other message is
377    ///   defined which must have a well known selector.
378    fn ensure_valid_wildcard_selector_usage(
379        items: &[ir::Item],
380    ) -> Result<(), syn::Error> {
381        let mut wildcard_selector: Option<&ir::Message> = None;
382        let mut other_messages = Vec::new();
383        for item_impl in items
384            .iter()
385            .filter_map(ir::Item::map_ink_item)
386            .filter_map(ir::InkItem::filter_map_impl_block)
387        {
388            for message in item_impl.iter_messages() {
389                if !message.has_wildcard_selector() {
390                    other_messages.push(message);
391                    continue
392                }
393                match wildcard_selector {
394                    None => wildcard_selector = Some(message.callable()),
395                    Some(overlap) => {
396                        let err = format_err!(
397                            message.callable().span(),
398                            "encountered ink! messages with overlapping wildcard selectors",
399                        );
400                        let overlap_err = format_err!(
401                            overlap.span(),
402                            "first ink! message with overlapping wildcard selector here",
403                        );
404                        return Err(err.into_combine(overlap_err))
405                    }
406                }
407            }
408
409            if let Some(wildcard) = wildcard_selector {
410                match other_messages.len() as u32 {
411                    0 => {
412                        return Err(format_err!(
413                            wildcard.span(),
414                            "missing definition of another message with TODO in tandem with a wildcard \
415                        selector",
416                        ))
417                    }
418                    1 => {
419                        if !other_messages[0]
420                            .callable()
421                            .has_wildcard_complement_selector()
422                        {
423                            return Err(format_err!(
424                                other_messages[0].callable().span(),
425                                "when using a wildcard selector `selector = _` for an ink! message \
426                                then the other message must use the wildcard complement `selector = @`"
427                            ))
428                        }
429                    }
430                    2.. => {
431                        let mut combined = format_err!(
432                            wildcard.span(),
433                            "exactly one other message must be defined together with a wildcard selector",
434                        );
435                        for message in &other_messages {
436                            if !message.callable().has_wildcard_complement_selector() {
437                                combined.combine(
438                                    format_err!(
439                                        message.callable().span(),
440                                        "additional message not permitted together with a wildcard selector",
441                                    )
442                                )
443                            }
444                        }
445                        return Err(combined)
446                    }
447                }
448            } else {
449                for message in &other_messages {
450                    if message.callable().has_wildcard_complement_selector() {
451                        return Err(format_err!(
452                            message.callable().span(),
453                            "encountered ink! message with wildcard complement `selector = @` but no \
454                             wildcard `selector = _` defined"
455                        ));
456                    }
457                }
458            }
459
460            let mut wildcard_selector: Option<&ir::Constructor> = None;
461            for constructor in item_impl.iter_constructors() {
462                if !constructor.has_wildcard_selector() {
463                    continue
464                }
465                match wildcard_selector {
466                    None => wildcard_selector = Some(constructor.callable()),
467                    Some(overlap) => {
468                        return Err(format_err!(
469                            constructor.callable().span(),
470                            "encountered ink! constructor with overlapping wildcard selectors",
471                        )
472                            .into_combine(format_err!(
473                            overlap.span(),
474                            "first ink! constructor with overlapping wildcard selector here",
475                        )))
476                    }
477                }
478            }
479        }
480        Ok(())
481    }
482}
483
484impl TryFrom<syn::ItemMod> for ItemMod {
485    type Error = syn::Error;
486
487    fn try_from(module: syn::ItemMod) -> Result<Self, Self::Error> {
488        let module_span = module.span();
489        idents_lint::ensure_no_ink_identifiers(&module)?;
490        let (brace, items) = match module.content {
491            Some((brace, items)) => (brace, items),
492            None => {
493                return Err(format_err_spanned!(
494                    module,
495                    "out-of-line ink! modules are not supported, use `#[ink::contract] mod name {{ ... }}`",
496                ))
497            }
498        };
499        let (ink_attrs, other_attrs) = ir::partition_attributes(module.attrs)?;
500        if !ink_attrs.is_empty() {
501            let mut error = format_err!(
502                module_span,
503                "encountered invalid ink! attributes on ink! module"
504            );
505            for ink_attr in ink_attrs {
506                error.combine(format_err!(
507                    ink_attr.span(),
508                    "invalid ink! attribute on module"
509                ))
510            }
511            return Err(error)
512        }
513        let items = items
514            .into_iter()
515            .map(<ir::Item as TryFrom<syn::Item>>::try_from)
516            .collect::<Result<Vec<_>, syn::Error>>()?;
517        Self::ensure_storage_struct_quantity(module_span, &items)?;
518        Self::ensure_contains_message(module_span, &items)?;
519        Self::ensure_contains_constructor(module_span, &items)?;
520        Self::ensure_no_overlapping_selectors(&items)?;
521        Self::ensure_valid_wildcard_selector_usage(&items)?;
522        Self::ensure_only_allowed_cfgs(&items)?;
523        Ok(Self {
524            attrs: other_attrs,
525            vis: module.vis,
526            mod_token: module.mod_token,
527            ident: module.ident,
528            brace,
529            items,
530        })
531    }
532}
533
534impl quote::ToTokens for ItemMod {
535    /// We mainly implement this trait for ink! module to have a derived
536    /// [`Spanned`](`syn::spanned::Spanned`) implementation for it.
537    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
538        tokens.append_all(
539            self.attrs
540                .iter()
541                .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
542        );
543        self.vis.to_tokens(tokens);
544        self.mod_token.to_tokens(tokens);
545        self.ident.to_tokens(tokens);
546        self.brace.surround(tokens, |tokens| {
547            tokens.append_all(
548                self.attrs
549                    .iter()
550                    .filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
551            );
552            tokens.append_all(&self.items);
553        });
554    }
555}
556
557impl ItemMod {
558    /// Returns the identifier of the ink! module.
559    pub fn ident(&self) -> &Ident {
560        &self.ident
561    }
562
563    /// Returns the storage struct definition for this ink! module.
564    ///
565    /// # Note
566    ///
567    /// The storage definition is the struct that has been annotated with
568    /// `#[ink(storage)]`. This struct is required to be defined in the root
569    /// of the ink! inline module.
570    ///
571    /// # Panics
572    ///
573    /// If zero or multiple `#[ink(storage)]` annotated structs were found in
574    /// the ink! module. This can be expected to never happen since upon
575    /// construction of an ink! module it is asserted that exactly one
576    /// `#[ink(storage)]` struct exists.
577    pub fn storage(&self) -> &ir::Storage {
578        let mut iter = IterInkItems::new(self)
579            .filter_map(|ink_item| ink_item.filter_map_storage_item());
580        let storage = iter
581            .next()
582            .expect("encountered ink! module without a storage struct");
583        assert!(
584            iter.next().is_none(),
585            "encountered multiple storage structs in ink! module"
586        );
587        storage
588    }
589
590    /// Returns all (ink! and non-ink! specific) item definitions of the ink! inline
591    /// module.
592    pub fn items(&self) -> &[ir::Item] {
593        self.items.as_slice()
594    }
595
596    /// Returns an iterator yielding all ink! implementation blocks.
597    ///
598    /// # Note
599    ///
600    /// An ink! implementation block can be either an inherent `impl` block
601    /// directly defined for the contract's storage struct if it includes at
602    /// least one `#[ink(message)]` or `#[ink(constructor)]` annotation, e.g.:
603    ///
604    /// ```
605    /// # use ink_ir as ir;
606    /// # <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
607    /// # mod my_module {
608    /// # #[ink(storage)]
609    /// # pub struct MyStorage {
610    /// #     /* storage fields */
611    /// # }
612    /// #
613    /// impl MyStorage {
614    /// #   #[ink(constructor)]
615    /// #   pub fn my_constructor() -> Self {
616    /// #       /* constructor implementation */
617    /// #   }
618    /// #
619    ///     #[ink(message)]
620    ///     pub fn my_message(&self) {
621    ///         // message implementation
622    ///     }
623    /// }
624    /// # }}).unwrap();
625    /// ```
626    ///
627    /// Also an implementation block can be defined as a trait implementation
628    /// for the ink! storage struct using the `#[ink(impl)]` annotation even
629    /// if none of its interior items have any ink! specific attributes on them,
630    /// e.g.:
631    ///
632    /// ```
633    /// # use ink_ir as ir;
634    /// # <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
635    /// # mod my_module {
636    /// # #[ink(storage)]
637    /// # pub struct MyStorage {
638    /// #     /* storage fields */
639    /// # }
640    /// #
641    /// #[ink(impl)]
642    /// impl MyStorage {
643    ///     fn my_method(&self) -> i32 {
644    ///         // method implementation
645    ///     }
646    /// }
647    /// #
648    /// # impl MyStorage {
649    /// #   #[ink(constructor)]
650    /// #   pub fn my_constructor() -> Self {
651    /// #       /* constructor implementation */
652    /// #   }
653    /// #
654    /// #   #[ink(message)]
655    /// #   pub fn my_message(&self) {
656    /// #       /* message implementation */
657    /// #   }
658    /// # }
659    /// # }}).unwrap();
660    /// ```
661    pub fn impls(&self) -> IterItemImpls<'_> {
662        IterItemImpls::new(self)
663    }
664
665    /// Returns an iterator yielding all event definitions in this ink! module.
666    pub fn events(&self) -> IterEvents<'_> {
667        IterEvents::new(self)
668    }
669
670    /// Returns all non-ink! attributes of the ink! module.
671    pub fn attrs(&self) -> &[syn::Attribute] {
672        &self.attrs
673    }
674
675    /// Returns the visibility of the ink! module.
676    pub fn vis(&self) -> &syn::Visibility {
677        &self.vis
678    }
679}
680
681/// Iterator yielding ink! item definitions of the ink! smart contract.
682pub struct IterInkItems<'a> {
683    items_iter: core::slice::Iter<'a, ir::Item>,
684}
685
686impl<'a> IterInkItems<'a> {
687    /// Creates a new ink! module items iterator.
688    fn new(ink_module: &'a ItemMod) -> Self {
689        Self {
690            items_iter: ink_module.items.iter(),
691        }
692    }
693}
694
695impl<'a> Iterator for IterInkItems<'a> {
696    type Item = &'a ir::InkItem;
697
698    fn next(&mut self) -> Option<Self::Item> {
699        'repeat: loop {
700            match self.items_iter.next() {
701                None => return None,
702                Some(item) => {
703                    if let Some(event) = item.map_ink_item() {
704                        return Some(event)
705                    }
706                    continue 'repeat
707                }
708            }
709        }
710    }
711}
712
713/// Iterator yielding all ink! event definitions within the ink!
714/// [`ItemMod`](`crate::ir::ItemMod`).
715pub struct IterEvents<'a> {
716    items_iter: IterInkItems<'a>,
717}
718
719impl<'a> IterEvents<'a> {
720    /// Creates a new ink! events iterator.
721    fn new(ink_module: &'a ItemMod) -> Self {
722        Self {
723            items_iter: IterInkItems::new(ink_module),
724        }
725    }
726}
727
728impl<'a> Iterator for IterEvents<'a> {
729    type Item = &'a ir::Event;
730
731    fn next(&mut self) -> Option<Self::Item> {
732        'repeat: loop {
733            match self.items_iter.next() {
734                None => return None,
735                Some(ink_item) => {
736                    if let Some(event) = ink_item.filter_map_event_item() {
737                        return Some(event)
738                    }
739                    continue 'repeat
740                }
741            }
742        }
743    }
744}
745
746/// Iterator yielding all ink! implementation block definitions within the ink!
747/// [`ItemMod`](`crate::ir::ItemMod`).
748pub struct IterItemImpls<'a> {
749    items_iter: IterInkItems<'a>,
750}
751
752impl<'a> IterItemImpls<'a> {
753    /// Creates a new ink! implementation blocks iterator.
754    fn new(ink_module: &'a ItemMod) -> Self {
755        Self {
756            items_iter: IterInkItems::new(ink_module),
757        }
758    }
759}
760
761impl<'a> Iterator for IterItemImpls<'a> {
762    type Item = &'a ir::ItemImpl;
763
764    fn next(&mut self) -> Option<Self::Item> {
765        'repeat: loop {
766            match self.items_iter.next() {
767                None => return None,
768                Some(ink_item) => {
769                    if let Some(event) = ink_item.filter_map_impl_block() {
770                        return Some(event)
771                    }
772                    continue 'repeat
773                }
774            }
775        }
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use crate as ir;
782
783    #[test]
784    fn item_mod_try_from_works() {
785        let item_mods: Vec<syn::ItemMod> = vec![
786            syn::parse_quote! {
787                mod minimal {
788                    #[ink(storage)]
789                    pub struct Minimal {}
790
791                    impl Minimal {
792                        #[ink(constructor)]
793                        pub fn new() -> Self {}
794                        #[ink(message)]
795                        pub fn minimal_message(&self) {}
796                    }
797                }
798            },
799            syn::parse_quote! {
800                mod flipper {
801                    #[ink(storage)]
802                    pub struct Flipper {
803                        value: bool,
804                    }
805
806                    impl Default for Flipper {
807                        #[ink(constructor)]
808                        fn default() -> Self {
809                            Self { value: false }
810                        }
811                    }
812
813                    impl Flipper {
814                        #[ink(message)]
815                        pub fn flip(&mut self) {
816                            self.value = !self.value
817                        }
818
819                        #[ink(message)]
820                        pub fn get(&self) -> bool {
821                            self.value
822                        }
823                    }
824                }
825            },
826        ];
827        for item_mod in item_mods {
828            assert!(<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod).is_ok())
829        }
830    }
831
832    fn assert_fail(item_mod: syn::ItemMod, expected_err: &str) {
833        assert_eq!(
834            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
835                .map_err(|err| err.to_string()),
836            Err(expected_err.to_string()),
837        );
838    }
839
840    #[test]
841    fn missing_storage_struct_fails() {
842        assert_fail(
843            syn::parse_quote! {
844                mod my_module {
845                    impl MyStorage {
846                        #[ink(constructor)]
847                        pub fn my_constructor() -> Self {}
848                        #[ink(message)]
849                        pub fn my_message(&self) {}
850                    }
851                }
852            },
853            "missing ink! storage struct",
854        )
855    }
856
857    #[test]
858    fn multiple_storage_struct_fails() {
859        assert_fail(
860            syn::parse_quote! {
861                mod my_module {
862                    #[ink(storage)]
863                    pub struct MyFirstStorage {}
864                    #[ink(storage)]
865                    pub struct MySecondStorage {}
866                    impl MyFirstStorage {
867                        #[ink(constructor)]
868                        pub fn my_constructor() -> Self {}
869                        #[ink(message)]
870                        pub fn my_message(&self) {}
871                    }
872                }
873            },
874            "encountered multiple ink! storage structs, expected exactly one",
875        )
876    }
877
878    #[test]
879    fn missing_constructor_fails() {
880        assert_fail(
881            syn::parse_quote! {
882                mod my_module {
883                    #[ink(storage)]
884                    pub struct MyStorage {}
885
886                    impl MyStorage {
887                        #[ink(message)]
888                        pub fn my_message(&self) {}
889                    }
890                }
891            },
892            "missing ink! constructor",
893        )
894    }
895
896    #[test]
897    fn missing_message_fails() {
898        assert_fail(
899            syn::parse_quote! {
900                mod my_module {
901                    #[ink(storage)]
902                    pub struct MyStorage {}
903
904                    impl MyStorage {
905                        #[ink(constructor)]
906                        pub fn my_constructor() -> Self {}
907                    }
908                }
909            },
910            "missing ink! message",
911        )
912    }
913
914    #[test]
915    fn invalid_out_of_line_module_fails() {
916        assert_fail(
917            syn::parse_quote! {
918                mod my_module;
919            },
920            "out-of-line ink! modules are not supported, use `#[ink::contract] mod name { ... }`",
921        )
922    }
923
924    #[test]
925    fn conflicting_attributes_fails() {
926        assert_fail(
927            syn::parse_quote! {
928                #[ink(namespace = "my_namespace")]
929                mod my_module {
930                    #[ink(storage)]
931                    pub struct MyStorage {}
932                    impl MyStorage {
933                        #[ink(constructor)]
934                        pub fn my_constructor() -> Self {}
935                        #[ink(message)]
936                        pub fn my_message(&self) {}
937                    }
938                }
939            },
940            "encountered invalid ink! attributes on ink! module",
941        )
942    }
943
944    #[test]
945    fn overlapping_messages_fails() {
946        assert_fail(
947            syn::parse_quote! {
948                mod my_module {
949                    #[ink(storage)]
950                    pub struct MyStorage {}
951
952                    impl MyStorage {
953                        #[ink(constructor)]
954                        pub fn my_constructor() -> Self {}
955
956                        #[ink(message, selector = 0xDEADBEEF)]
957                        pub fn my_message_1(&self) {}
958                    }
959
960                    impl MyStorage {
961                        #[ink(message, selector = 0xDEADBEEF)]
962                        pub fn my_message_2(&self) {}
963                    }
964                }
965            },
966            "encountered ink! messages with overlapping selectors (= [DE, AD, BE, EF])\n\
967            hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
968            on the implementation block to disambiguate overlapping selectors.",
969        );
970    }
971
972    #[test]
973    fn overlapping_constructors_fails() {
974        assert_fail(
975            syn::parse_quote! {
976                mod my_module {
977                    #[ink(storage)]
978                    pub struct MyStorage {}
979
980                    impl MyStorage {
981                        #[ink(constructor, selector = 0xDEADBEEF)]
982                        pub fn my_constructor_1() -> Self {}
983
984                        #[ink(message)]
985                        pub fn my_message_1(&self) {}
986                    }
987
988                    impl MyStorage {
989                        #[ink(constructor, selector = 0xDEADBEEF)]
990                        pub fn my_constructor_2() -> Self {}
991                    }
992                }
993            },
994            "encountered ink! constructors with overlapping selectors (= [DE, AD, BE, EF])\n\
995            hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
996            on the implementation block to disambiguate overlapping selectors.",
997        );
998    }
999
1000    #[test]
1001    fn overlapping_trait_impls_fails() {
1002        assert_fail(
1003            syn::parse_quote! {
1004                mod my_module {
1005                    #[ink(storage)]
1006                    pub struct MyStorage {}
1007
1008                    impl first::MyTrait for MyStorage {
1009                        #[ink(constructor)]
1010                        fn my_constructor() -> Self {}
1011
1012                        #[ink(message)]
1013                        fn my_message(&self) {}
1014                    }
1015
1016                    impl second::MyTrait for MyStorage {
1017                        #[ink(message)]
1018                        fn my_message(&self) {}
1019                    }
1020                }
1021            },
1022            "encountered ink! messages with overlapping selectors (= [04, C4, 94, 46])\n\
1023            hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
1024            on the implementation block to disambiguate overlapping selectors.",
1025        );
1026    }
1027
1028    #[test]
1029    fn allow_overlap_between_messages_and_constructors() {
1030        assert!(
1031            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1032                mod my_module {
1033                    #[ink(storage)]
1034                    pub struct MyStorage {}
1035
1036                    impl MyStorage {
1037                        #[ink(constructor, selector = 0xDEADBEEF)]
1038                        pub fn my_constructor() -> Self {}
1039
1040                        #[ink(message, selector = 0xDEADBEEF)]
1041                        pub fn my_message(&self) {}
1042                    }
1043                }
1044            })
1045            .is_ok()
1046        );
1047    }
1048
1049    #[test]
1050    fn overlapping_wildcard_selectors_fails() {
1051        assert_fail(
1052            syn::parse_quote! {
1053                mod my_module {
1054                    #[ink(storage)]
1055                    pub struct MyStorage {}
1056
1057                    impl MyStorage {
1058                        #[ink(constructor)]
1059                        pub fn my_constructor() -> Self {}
1060
1061                        #[ink(message, selector = _)]
1062                        pub fn my_message1(&self) {}
1063
1064                        #[ink(message, selector = _)]
1065                        pub fn my_message2(&self) {}
1066                    }
1067                }
1068            },
1069            "encountered ink! messages with overlapping wildcard selectors",
1070        );
1071    }
1072
1073    #[test]
1074    fn wildcard_selector_on_constructor_works() {
1075        assert!(
1076            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1077                mod my_module {
1078                    #[ink(storage)]
1079                    pub struct MyStorage {}
1080
1081                    impl MyStorage {
1082                        #[ink(constructor, selector = _)]
1083                        pub fn my_constructor() -> Self {}
1084
1085                        #[ink(message)]
1086                        pub fn my_message(&self) {}
1087                    }
1088                }
1089            })
1090            .is_ok()
1091        );
1092    }
1093
1094    #[test]
1095    fn overlap_between_wildcard_selector_and_composed_selector_fails() {
1096        assert_fail(
1097            syn::parse_quote! {
1098                mod my_module {
1099                    #[ink(storage)]
1100                    pub struct MyStorage {}
1101
1102                    impl MyStorage {
1103                        #[ink(constructor)]
1104                        pub fn my_constructor() -> Self {}
1105
1106                        #[ink(message, selector = _, selector = 0xCAFEBABE)]
1107                        pub fn my_message(&self) {}
1108                    }
1109                }
1110            },
1111            "encountered ink! attribute arguments with equal kinds",
1112        );
1113    }
1114
1115    #[test]
1116    fn wildcard_selector_and_one_other_message_with_well_known_selector_works() {
1117        assert!(
1118            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1119                mod my_module {
1120                    #[ink(storage)]
1121                    pub struct MyStorage {}
1122
1123                    impl MyStorage {
1124                        #[ink(constructor)]
1125                        pub fn my_constructor() -> Self {}
1126
1127                        #[ink(message, selector = _)]
1128                        pub fn fallback(&self) {}
1129
1130                        #[ink(message, selector = 0x9BAE9D5E)]
1131                        pub fn wildcard_complement_message(&self) {}
1132                    }
1133                }
1134            })
1135            .is_ok()
1136        );
1137    }
1138
1139    #[test]
1140    fn wildcard_selector_and_one_other_message_with_wildcard_complement_selector_works() {
1141        assert!(
1142            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1143                mod my_module {
1144                    #[ink(storage)]
1145                    pub struct MyStorage {}
1146
1147                    impl MyStorage {
1148                        #[ink(constructor)]
1149                        pub fn my_constructor() -> Self {}
1150
1151                        #[ink(message, selector = _)]
1152                        pub fn fallback(&self) {}
1153
1154                        #[ink(message, selector = @)]
1155                        pub fn wildcard_complement_message(&self) {}
1156                    }
1157                }
1158            })
1159            .is_ok()
1160        );
1161    }
1162
1163    #[test]
1164    fn wildcard_selector_without_other_message_fails() {
1165        assert_fail(
1166            syn::parse_quote! {
1167                mod my_module {
1168                    #[ink(storage)]
1169                    pub struct MyStorage {}
1170
1171                    impl MyStorage {
1172                        #[ink(constructor)]
1173                        pub fn my_constructor() -> Self {}
1174
1175                        #[ink(message, selector = _)]
1176                        pub fn fallback(&self) {}
1177                    }
1178                }
1179            },
1180            "missing definition of another message with TODO in tandem with a wildcard selector",
1181        )
1182    }
1183
1184    #[test]
1185    fn wildcard_selector_and_one_other_message_without_well_known_selector_fails() {
1186        assert_fail(
1187            syn::parse_quote! {
1188                mod my_module {
1189                    #[ink(storage)]
1190                    pub struct MyStorage {}
1191
1192                    impl MyStorage {
1193                        #[ink(constructor)]
1194                        pub fn my_constructor() -> Self {}
1195
1196                        #[ink(message, selector = _)]
1197                        pub fn fallback(&self) {}
1198
1199                        #[ink(message)]
1200                        pub fn other_message_without_well_known_selector(&self) {}
1201                    }
1202                }
1203            },
1204            "when using a wildcard selector `selector = _` for an ink! message then the other \
1205            message must use the wildcard complement `selector = @`",
1206        );
1207    }
1208
1209    #[test]
1210    fn wildcard_selector_with_two_other_messages() {
1211        assert_fail(
1212            syn::parse_quote! {
1213                mod my_module {
1214                    #[ink(storage)]
1215                    pub struct MyStorage {}
1216
1217                    impl MyStorage {
1218                        #[ink(constructor)]
1219                        pub fn my_constructor() -> Self {}
1220
1221                        #[ink(message, selector = _)]
1222                        pub fn fallback(&self) {}
1223
1224                        #[ink(message, selector = 0x00000000)]
1225                        pub fn wildcard_complement_message(&self) {}
1226
1227                        #[ink(message)]
1228                        pub fn another_message_not_allowed(&self) {}
1229                    }
1230                }
1231            },
1232            "exactly one other message must be defined together with a wildcard selector",
1233        );
1234    }
1235
1236    #[test]
1237    fn wildcard_selector_with_many_other_messages() {
1238        assert_fail(
1239            syn::parse_quote! {
1240                mod my_module {
1241                    #[ink(storage)]
1242                    pub struct MyStorage {}
1243
1244                    impl MyStorage {
1245                        #[ink(constructor)]
1246                        pub fn my_constructor() -> Self {}
1247
1248                        #[ink(message, selector = _)]
1249                        pub fn fallback(&self) {}
1250
1251                        #[ink(message, selector = @)]
1252                        pub fn wildcard_complement(&self) {}
1253
1254                        #[ink(message)]
1255                        pub fn another_message_not_allowed1(&self) {}
1256
1257                        #[ink(message)]
1258                        pub fn another_message_not_allowed2(&self) {}
1259
1260                        #[ink(message)]
1261                        pub fn another_message_not_allowed3(&self) {}
1262                    }
1263                }
1264            },
1265            "exactly one other message must be defined together with a wildcard selector",
1266        );
1267    }
1268
1269    #[test]
1270    fn wildcard_complement_used_without_wildcard_fails() {
1271        assert_fail(
1272            syn::parse_quote! {
1273                mod my_module {
1274                    #[ink(storage)]
1275                    pub struct MyStorage {}
1276
1277                    impl MyStorage {
1278                        #[ink(constructor)]
1279                        pub fn my_constructor() -> Self {}
1280
1281                        #[ink(message, selector = @)]
1282                        pub fn uses_reserved_wildcard_other_message_selector(&self) {}
1283                    }
1284                }
1285            },
1286            "encountered ink! message with wildcard complement `selector = @` but no \
1287            wildcard `selector = _` defined",
1288        )
1289    }
1290
1291    #[test]
1292    fn wildcard_reserved_selector_used_without_wildcard_fails() {
1293        assert_fail(
1294            syn::parse_quote! {
1295                mod my_module {
1296                    #[ink(storage)]
1297                    pub struct MyStorage {}
1298
1299                    impl MyStorage {
1300                        #[ink(constructor)]
1301                        pub fn my_constructor() -> Self {}
1302
1303                        #[ink(message, selector = 0x9BAE9D5E)]
1304                        pub fn uses_reserved_wildcard_other_message_selector(&self) {}
1305                    }
1306                }
1307            },
1308            "encountered ink! message with wildcard complement `selector = @` but no \
1309            wildcard `selector = _` defined",
1310        )
1311    }
1312
1313    #[test]
1314    fn cfg_feature_std_not_allowed() {
1315        let item_mod = syn::parse_quote! {
1316            mod my_module {
1317                #[ink(storage)]
1318                pub struct MyStorage {}
1319
1320                impl MyStorage {
1321                    #[ink(constructor)]
1322                    pub fn my_constructor() -> Self {}
1323
1324                    #[ink(message)]
1325                    #[cfg(feature = "std")]
1326                    pub fn not_allowed(&self) {}
1327                }
1328            }
1329        };
1330        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1331            .map_err(|err| err.to_string());
1332        assert!(res.is_err());
1333        assert!(
1334            res.unwrap_err()
1335                .starts_with("The feature `std` is not allowed in `cfg`.")
1336        );
1337    }
1338
1339    #[test]
1340    fn cfg_feature_other_than_std_allowed() {
1341        let item_mod = syn::parse_quote! {
1342            mod my_module {
1343                #[ink(storage)]
1344                pub struct MyStorage {}
1345
1346                impl MyStorage {
1347                    #[ink(constructor)]
1348                    pub fn my_constructor() -> Self {}
1349
1350                    #[ink(message)]
1351                    pub fn not_allowed(&self) {}
1352                }
1353            }
1354        };
1355        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1356            .map_err(|err| err.to_string());
1357        assert!(res.is_ok());
1358    }
1359
1360    #[test]
1361    fn cfg_test_allowed() {
1362        let item_mod = syn::parse_quote! {
1363            mod my_module {
1364                #[ink(storage)]
1365                pub struct MyStorage {}
1366
1367                impl MyStorage {
1368                    #[ink(constructor)]
1369                    pub fn my_constructor() -> Self {}
1370
1371                    #[ink(message)]
1372                    #[cfg(test)]
1373                    pub fn not_allowed(&self) {}
1374                }
1375            }
1376        };
1377        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1378            .map_err(|err| err.to_string());
1379        assert!(res.is_ok());
1380    }
1381
1382    #[test]
1383    fn cfg_nested_forbidden_must_be_found() {
1384        let item_mod = syn::parse_quote! {
1385            mod my_module {
1386                #[ink(storage)]
1387                pub struct MyStorage {}
1388
1389                impl MyStorage {
1390                    #[ink(constructor)]
1391                    #[cfg(any(not(target_os = "wasm")))]
1392                    pub fn my_constructor() -> Self {}
1393
1394                    #[ink(message)]
1395                    pub fn not_allowed(&self) {}
1396                }
1397            }
1398        };
1399        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1400            .map_err(|err| err.to_string());
1401        assert!(res.is_err());
1402        assert!(
1403            res.unwrap_err()
1404                .starts_with("This `cfg` attribute is not allowed.")
1405        );
1406    }
1407}