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    error::ExtError as _,
17    ir,
18    ir::idents_lint,
19    Callable,
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                        if ident.eq("cfg") {
303                            return list.parse_nested_meta(verify_cfg_attrs);
304                        }
305                    }
306                    unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for other `List`");
307                }
308                syn::Meta::Path(_) => {
309                    // not relevant, we are only looking at `#[cfg(…)]`
310                    unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for `Path`");
311                }
312                syn::Meta::NameValue(_) => {
313                    // not relevant, we are only looking at `#[cfg(…)]`
314                    unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for `NameValue`");
315                }
316            }
317        }
318
319        fn verify_cfg_attrs(meta: syn::meta::ParseNestedMeta) -> Result<(), syn::Error> {
320            if meta.path.is_ident("test") {
321                return Ok(());
322            }
323            if meta.path.is_ident("any")
324                || meta.path.is_ident("all")
325                || meta.path.is_ident("not")
326            {
327                return meta.parse_nested_meta(verify_cfg_attrs);
328            }
329
330            if meta.path.is_ident("feature") {
331                let value = meta.value()?;
332                let value: syn::LitStr = value.parse()?;
333                if value.value().eq("std") {
334                    return Err(format_err_spanned!(
335                        meta.path,
336                        "The feature `std` is not allowed in `cfg`.\n\n{ERR_HELP}"
337                    ))
338                }
339                return Ok(());
340            }
341
342            Err(format_err_spanned!(
343                meta.path,
344                "This `cfg` attribute is not allowed.\n\n{ERR_HELP}"
345            ))
346        }
347
348        for item_impl in items
349            .iter()
350            .filter_map(ir::Item::map_ink_item)
351            .filter_map(ir::InkItem::filter_map_impl_block)
352        {
353            for message in item_impl.iter_messages() {
354                for a in message.get_cfg_syn_attrs() {
355                    verify_attr(&a)?;
356                }
357            }
358            for constructor in item_impl.iter_constructors() {
359                for a in constructor.get_cfg_syn_attrs() {
360                    verify_attr(&a)?;
361                }
362            }
363        }
364        Ok(())
365    }
366
367    /// Ensures that:
368    /// - At most one wildcard selector exists among ink! messages, as well as ink!
369    ///   constructors.
370    /// - Where a wildcard selector is defined for a message, at most one other message is
371    ///   defined which must have a well known selector.
372    fn ensure_valid_wildcard_selector_usage(
373        items: &[ir::Item],
374    ) -> Result<(), syn::Error> {
375        let mut wildcard_selector: Option<&ir::Message> = None;
376        let mut other_messages = Vec::new();
377        for item_impl in items
378            .iter()
379            .filter_map(ir::Item::map_ink_item)
380            .filter_map(ir::InkItem::filter_map_impl_block)
381        {
382            for message in item_impl.iter_messages() {
383                if !message.has_wildcard_selector() {
384                    other_messages.push(message);
385                    continue
386                }
387                match wildcard_selector {
388                    None => wildcard_selector = Some(message.callable()),
389                    Some(overlap) => {
390                        let err = format_err!(
391                            message.callable().span(),
392                            "encountered ink! messages with overlapping wildcard selectors",
393                        );
394                        let overlap_err = format_err!(
395                            overlap.span(),
396                            "first ink! message with overlapping wildcard selector here",
397                        );
398                        return Err(err.into_combine(overlap_err))
399                    }
400                }
401            }
402
403            if let Some(wildcard) = wildcard_selector {
404                match other_messages.len() as u32 {
405                    0 => return Err(format_err!(
406                        wildcard.span(),
407                        "missing definition of another message with TODO in tandem with a wildcard \
408                        selector",
409                    )),
410                    1 => {
411                        if !other_messages[0].callable().has_wildcard_complement_selector() {
412                            return Err(format_err!(
413                                other_messages[0].callable().span(),
414                                "when using a wildcard selector `selector = _` for an ink! message \
415                                then the other message must use the wildcard complement `selector = @`"
416                            ))
417                        }
418                    }
419                    2.. => {
420                        let mut combined = format_err!(
421                            wildcard.span(),
422                            "exactly one other message must be defined together with a wildcard selector",
423                        );
424                        for message in &other_messages {
425                            if !message.callable().has_wildcard_complement_selector() {
426                                combined.combine(
427                                    format_err!(
428                                        message.callable().span(),
429                                        "additional message not permitted together with a wildcard selector",
430                                    )
431                                )
432                            }
433                        }
434                        return Err(combined)
435                    }
436                }
437            } else {
438                for message in &other_messages {
439                    if message.callable().has_wildcard_complement_selector() {
440                        return Err(format_err!(
441                            message.callable().span(),
442                            "encountered ink! message with wildcard complement `selector = @` but no \
443                             wildcard `selector = _` defined"
444                        ));
445                    }
446                }
447            }
448
449            let mut wildcard_selector: Option<&ir::Constructor> = None;
450            for constructor in item_impl.iter_constructors() {
451                if !constructor.has_wildcard_selector() {
452                    continue
453                }
454                match wildcard_selector {
455                    None => wildcard_selector = Some(constructor.callable()),
456                    Some(overlap) => {
457                        return Err(format_err!(
458                            constructor.callable().span(),
459                            "encountered ink! constructor with overlapping wildcard selectors",
460                        )
461                            .into_combine(format_err!(
462                            overlap.span(),
463                            "first ink! constructor with overlapping wildcard selector here",
464                        )))
465                    }
466                }
467            }
468        }
469        Ok(())
470    }
471}
472
473impl TryFrom<syn::ItemMod> for ItemMod {
474    type Error = syn::Error;
475
476    fn try_from(module: syn::ItemMod) -> Result<Self, Self::Error> {
477        let module_span = module.span();
478        idents_lint::ensure_no_ink_identifiers(&module)?;
479        let (brace, items) = match module.content {
480            Some((brace, items)) => (brace, items),
481            None => {
482                return Err(format_err_spanned!(
483                    module,
484                    "out-of-line ink! modules are not supported, use `#[ink::contract] mod name {{ ... }}`",
485                ))
486            }
487        };
488        let (ink_attrs, other_attrs) = ir::partition_attributes(module.attrs)?;
489        if !ink_attrs.is_empty() {
490            let mut error = format_err!(
491                module_span,
492                "encountered invalid ink! attributes on ink! module"
493            );
494            for ink_attr in ink_attrs {
495                error.combine(format_err!(
496                    ink_attr.span(),
497                    "invalid ink! attribute on module"
498                ))
499            }
500            return Err(error)
501        }
502        let items = items
503            .into_iter()
504            .map(<ir::Item as TryFrom<syn::Item>>::try_from)
505            .collect::<Result<Vec<_>, syn::Error>>()?;
506        Self::ensure_storage_struct_quantity(module_span, &items)?;
507        Self::ensure_contains_message(module_span, &items)?;
508        Self::ensure_contains_constructor(module_span, &items)?;
509        Self::ensure_no_overlapping_selectors(&items)?;
510        Self::ensure_valid_wildcard_selector_usage(&items)?;
511        Self::ensure_only_allowed_cfgs(&items)?;
512        Ok(Self {
513            attrs: other_attrs,
514            vis: module.vis,
515            mod_token: module.mod_token,
516            ident: module.ident,
517            brace,
518            items,
519        })
520    }
521}
522
523impl quote::ToTokens for ItemMod {
524    /// We mainly implement this trait for ink! module to have a derived
525    /// [`Spanned`](`syn::spanned::Spanned`) implementation for it.
526    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
527        tokens.append_all(
528            self.attrs
529                .iter()
530                .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
531        );
532        self.vis.to_tokens(tokens);
533        self.mod_token.to_tokens(tokens);
534        self.ident.to_tokens(tokens);
535        self.brace.surround(tokens, |tokens| {
536            tokens.append_all(
537                self.attrs
538                    .iter()
539                    .filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
540            );
541            tokens.append_all(&self.items);
542        });
543    }
544}
545
546impl ItemMod {
547    /// Returns the identifier of the ink! module.
548    pub fn ident(&self) -> &Ident {
549        &self.ident
550    }
551
552    /// Returns the storage struct definition for this ink! module.
553    ///
554    /// # Note
555    ///
556    /// The storage definition is the struct that has been annotated with
557    /// `#[ink(storage)]`. This struct is required to be defined in the root
558    /// of the ink! inline module.
559    ///
560    /// # Panics
561    ///
562    /// If zero or multiple `#[ink(storage)]` annotated structs were found in
563    /// the ink! module. This can be expected to never happen since upon
564    /// construction of an ink! module it is asserted that exactly one
565    /// `#[ink(storage)]` struct exists.
566    pub fn storage(&self) -> &ir::Storage {
567        let mut iter = IterInkItems::new(self)
568            .filter_map(|ink_item| ink_item.filter_map_storage_item());
569        let storage = iter
570            .next()
571            .expect("encountered ink! module without a storage struct");
572        assert!(
573            iter.next().is_none(),
574            "encountered multiple storage structs in ink! module"
575        );
576        storage
577    }
578
579    /// Returns all (ink! and non-ink! specific) item definitions of the ink! inline
580    /// module.
581    pub fn items(&self) -> &[ir::Item] {
582        self.items.as_slice()
583    }
584
585    /// Returns an iterator yielding all ink! implementation blocks.
586    ///
587    /// # Note
588    ///
589    /// An ink! implementation block can be either an inherent `impl` block
590    /// directly defined for the contract's storage struct if it includes at
591    /// least one `#[ink(message)]` or `#[ink(constructor)]` annotation, e.g.:
592    ///
593    /// ```
594    /// # use ink_ir as ir;
595    /// # <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
596    /// # mod my_module {
597    /// # #[ink(storage)]
598    /// # pub struct MyStorage {
599    /// #     /* storage fields */
600    /// # }
601    /// #
602    /// impl MyStorage {
603    /// #   #[ink(constructor)]
604    /// #   pub fn my_constructor() -> Self {
605    /// #       /* constructor implementation */
606    /// #   }
607    /// #
608    ///     #[ink(message)]
609    ///     pub fn my_message(&self) {
610    ///         // message implementation
611    ///     }
612    /// }
613    /// # }}).unwrap();
614    /// ```
615    ///
616    /// Also an implementation block can be defined as a trait implementation
617    /// for the ink! storage struct using the `#[ink(impl)]` annotation even
618    /// if none of its interior items have any ink! specific attributes on them,
619    /// e.g.:
620    ///
621    /// ```
622    /// # use ink_ir as ir;
623    /// # <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
624    /// # mod my_module {
625    /// # #[ink(storage)]
626    /// # pub struct MyStorage {
627    /// #     /* storage fields */
628    /// # }
629    /// #
630    /// #[ink(impl)]
631    /// impl MyStorage {
632    ///     fn my_method(&self) -> i32 {
633    ///         // method implementation
634    ///     }
635    /// }
636    /// #
637    /// # impl MyStorage {
638    /// #   #[ink(constructor)]
639    /// #   pub fn my_constructor() -> Self {
640    /// #       /* constructor implementation */
641    /// #   }
642    /// #
643    /// #   #[ink(message)]
644    /// #   pub fn my_message(&self) {
645    /// #       /* message implementation */
646    /// #   }
647    /// # }
648    /// # }}).unwrap();
649    /// ```
650    pub fn impls(&self) -> IterItemImpls<'_> {
651        IterItemImpls::new(self)
652    }
653
654    /// Returns an iterator yielding all event definitions in this ink! module.
655    pub fn events(&self) -> IterEvents<'_> {
656        IterEvents::new(self)
657    }
658
659    /// Returns all non-ink! attributes of the ink! module.
660    pub fn attrs(&self) -> &[syn::Attribute] {
661        &self.attrs
662    }
663
664    /// Returns the visibility of the ink! module.
665    pub fn vis(&self) -> &syn::Visibility {
666        &self.vis
667    }
668}
669
670/// Iterator yielding ink! item definitions of the ink! smart contract.
671pub struct IterInkItems<'a> {
672    items_iter: core::slice::Iter<'a, ir::Item>,
673}
674
675impl<'a> IterInkItems<'a> {
676    /// Creates a new ink! module items iterator.
677    fn new(ink_module: &'a ItemMod) -> Self {
678        Self {
679            items_iter: ink_module.items.iter(),
680        }
681    }
682}
683
684impl<'a> Iterator for IterInkItems<'a> {
685    type Item = &'a ir::InkItem;
686
687    fn next(&mut self) -> Option<Self::Item> {
688        'repeat: loop {
689            match self.items_iter.next() {
690                None => return None,
691                Some(item) => {
692                    if let Some(event) = item.map_ink_item() {
693                        return Some(event)
694                    }
695                    continue 'repeat
696                }
697            }
698        }
699    }
700}
701
702/// Iterator yielding all ink! event definitions within the ink!
703/// [`ItemMod`](`crate::ir::ItemMod`).
704pub struct IterEvents<'a> {
705    items_iter: IterInkItems<'a>,
706}
707
708impl<'a> IterEvents<'a> {
709    /// Creates a new ink! events iterator.
710    fn new(ink_module: &'a ItemMod) -> Self {
711        Self {
712            items_iter: IterInkItems::new(ink_module),
713        }
714    }
715}
716
717impl<'a> Iterator for IterEvents<'a> {
718    type Item = &'a ir::Event;
719
720    fn next(&mut self) -> Option<Self::Item> {
721        'repeat: loop {
722            match self.items_iter.next() {
723                None => return None,
724                Some(ink_item) => {
725                    if let Some(event) = ink_item.filter_map_event_item() {
726                        return Some(event)
727                    }
728                    continue 'repeat
729                }
730            }
731        }
732    }
733}
734
735/// Iterator yielding all ink! implementation block definitions within the ink!
736/// [`ItemMod`](`crate::ir::ItemMod`).
737pub struct IterItemImpls<'a> {
738    items_iter: IterInkItems<'a>,
739}
740
741impl<'a> IterItemImpls<'a> {
742    /// Creates a new ink! implementation blocks iterator.
743    fn new(ink_module: &'a ItemMod) -> Self {
744        Self {
745            items_iter: IterInkItems::new(ink_module),
746        }
747    }
748}
749
750impl<'a> Iterator for IterItemImpls<'a> {
751    type Item = &'a ir::ItemImpl;
752
753    fn next(&mut self) -> Option<Self::Item> {
754        'repeat: loop {
755            match self.items_iter.next() {
756                None => return None,
757                Some(ink_item) => {
758                    if let Some(event) = ink_item.filter_map_impl_block() {
759                        return Some(event)
760                    }
761                    continue 'repeat
762                }
763            }
764        }
765    }
766}
767
768#[cfg(test)]
769mod tests {
770    use crate as ir;
771
772    #[test]
773    fn item_mod_try_from_works() {
774        let item_mods: Vec<syn::ItemMod> = vec![
775            syn::parse_quote! {
776                mod minimal {
777                    #[ink(storage)]
778                    pub struct Minimal {}
779
780                    impl Minimal {
781                        #[ink(constructor)]
782                        pub fn new() -> Self {}
783                        #[ink(message)]
784                        pub fn minimal_message(&self) {}
785                    }
786                }
787            },
788            syn::parse_quote! {
789                mod flipper {
790                    #[ink(storage)]
791                    pub struct Flipper {
792                        value: bool,
793                    }
794
795                    impl Default for Flipper {
796                        #[ink(constructor)]
797                        fn default() -> Self {
798                            Self { value: false }
799                        }
800                    }
801
802                    impl Flipper {
803                        #[ink(message)]
804                        pub fn flip(&mut self) {
805                            self.value = !self.value
806                        }
807
808                        #[ink(message)]
809                        pub fn get(&self) -> bool {
810                            self.value
811                        }
812                    }
813                }
814            },
815        ];
816        for item_mod in item_mods {
817            assert!(<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod).is_ok())
818        }
819    }
820
821    fn assert_fail(item_mod: syn::ItemMod, expected_err: &str) {
822        assert_eq!(
823            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
824                .map_err(|err| err.to_string()),
825            Err(expected_err.to_string()),
826        );
827    }
828
829    #[test]
830    fn missing_storage_struct_fails() {
831        assert_fail(
832            syn::parse_quote! {
833                mod my_module {
834                    impl MyStorage {
835                        #[ink(constructor)]
836                        pub fn my_constructor() -> Self {}
837                        #[ink(message)]
838                        pub fn my_message(&self) {}
839                    }
840                }
841            },
842            "missing ink! storage struct",
843        )
844    }
845
846    #[test]
847    fn multiple_storage_struct_fails() {
848        assert_fail(
849            syn::parse_quote! {
850                mod my_module {
851                    #[ink(storage)]
852                    pub struct MyFirstStorage {}
853                    #[ink(storage)]
854                    pub struct MySecondStorage {}
855                    impl MyFirstStorage {
856                        #[ink(constructor)]
857                        pub fn my_constructor() -> Self {}
858                        #[ink(message)]
859                        pub fn my_message(&self) {}
860                    }
861                }
862            },
863            "encountered multiple ink! storage structs, expected exactly one",
864        )
865    }
866
867    #[test]
868    fn missing_constructor_fails() {
869        assert_fail(
870            syn::parse_quote! {
871                mod my_module {
872                    #[ink(storage)]
873                    pub struct MyStorage {}
874
875                    impl MyStorage {
876                        #[ink(message)]
877                        pub fn my_message(&self) {}
878                    }
879                }
880            },
881            "missing ink! constructor",
882        )
883    }
884
885    #[test]
886    fn missing_message_fails() {
887        assert_fail(
888            syn::parse_quote! {
889                mod my_module {
890                    #[ink(storage)]
891                    pub struct MyStorage {}
892
893                    impl MyStorage {
894                        #[ink(constructor)]
895                        pub fn my_constructor() -> Self {}
896                    }
897                }
898            },
899            "missing ink! message",
900        )
901    }
902
903    #[test]
904    fn invalid_out_of_line_module_fails() {
905        assert_fail(
906            syn::parse_quote! {
907                mod my_module;
908            },
909            "out-of-line ink! modules are not supported, use `#[ink::contract] mod name { ... }`",
910        )
911    }
912
913    #[test]
914    fn conflicting_attributes_fails() {
915        assert_fail(
916            syn::parse_quote! {
917                #[ink(namespace = "my_namespace")]
918                mod my_module {
919                    #[ink(storage)]
920                    pub struct MyStorage {}
921                    impl MyStorage {
922                        #[ink(constructor)]
923                        pub fn my_constructor() -> Self {}
924                        #[ink(message)]
925                        pub fn my_message(&self) {}
926                    }
927                }
928            },
929            "encountered invalid ink! attributes on ink! module",
930        )
931    }
932
933    #[test]
934    fn overlapping_messages_fails() {
935        assert_fail(
936            syn::parse_quote! {
937                mod my_module {
938                    #[ink(storage)]
939                    pub struct MyStorage {}
940
941                    impl MyStorage {
942                        #[ink(constructor)]
943                        pub fn my_constructor() -> Self {}
944
945                        #[ink(message, selector = 0xDEADBEEF)]
946                        pub fn my_message_1(&self) {}
947                    }
948
949                    impl MyStorage {
950                        #[ink(message, selector = 0xDEADBEEF)]
951                        pub fn my_message_2(&self) {}
952                    }
953                }
954            },
955            "encountered ink! messages with overlapping selectors (= [DE, AD, BE, EF])\n\
956            hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
957            on the implementation block to disambiguate overlapping selectors.",
958        );
959    }
960
961    #[test]
962    fn overlapping_constructors_fails() {
963        assert_fail(
964            syn::parse_quote! {
965                mod my_module {
966                    #[ink(storage)]
967                    pub struct MyStorage {}
968
969                    impl MyStorage {
970                        #[ink(constructor, selector = 0xDEADBEEF)]
971                        pub fn my_constructor_1() -> Self {}
972
973                        #[ink(message)]
974                        pub fn my_message_1(&self) {}
975                    }
976
977                    impl MyStorage {
978                        #[ink(constructor, selector = 0xDEADBEEF)]
979                        pub fn my_constructor_2() -> Self {}
980                    }
981                }
982            },
983            "encountered ink! constructors with overlapping selectors (= [DE, AD, BE, EF])\n\
984            hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
985            on the implementation block to disambiguate overlapping selectors.",
986        );
987    }
988
989    #[test]
990    fn overlapping_trait_impls_fails() {
991        assert_fail(
992            syn::parse_quote! {
993                mod my_module {
994                    #[ink(storage)]
995                    pub struct MyStorage {}
996
997                    impl first::MyTrait for MyStorage {
998                        #[ink(constructor)]
999                        fn my_constructor() -> Self {}
1000
1001                        #[ink(message)]
1002                        fn my_message(&self) {}
1003                    }
1004
1005                    impl second::MyTrait for MyStorage {
1006                        #[ink(message)]
1007                        fn my_message(&self) {}
1008                    }
1009                }
1010            },
1011            "encountered ink! messages with overlapping selectors (= [04, C4, 94, 46])\n\
1012            hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
1013            on the implementation block to disambiguate overlapping selectors.",
1014        );
1015    }
1016
1017    #[test]
1018    fn allow_overlap_between_messages_and_constructors() {
1019        assert!(
1020            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1021                mod my_module {
1022                    #[ink(storage)]
1023                    pub struct MyStorage {}
1024
1025                    impl MyStorage {
1026                        #[ink(constructor, selector = 0xDEADBEEF)]
1027                        pub fn my_constructor() -> Self {}
1028
1029                        #[ink(message, selector = 0xDEADBEEF)]
1030                        pub fn my_message(&self) {}
1031                    }
1032                }
1033            })
1034            .is_ok()
1035        );
1036    }
1037
1038    #[test]
1039    fn overlapping_wildcard_selectors_fails() {
1040        assert_fail(
1041            syn::parse_quote! {
1042                mod my_module {
1043                    #[ink(storage)]
1044                    pub struct MyStorage {}
1045
1046                    impl MyStorage {
1047                        #[ink(constructor)]
1048                        pub fn my_constructor() -> Self {}
1049
1050                        #[ink(message, selector = _)]
1051                        pub fn my_message1(&self) {}
1052
1053                        #[ink(message, selector = _)]
1054                        pub fn my_message2(&self) {}
1055                    }
1056                }
1057            },
1058            "encountered ink! messages with overlapping wildcard selectors",
1059        );
1060    }
1061
1062    #[test]
1063    fn wildcard_selector_on_constructor_works() {
1064        assert!(
1065            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1066                mod my_module {
1067                    #[ink(storage)]
1068                    pub struct MyStorage {}
1069
1070                    impl MyStorage {
1071                        #[ink(constructor, selector = _)]
1072                        pub fn my_constructor() -> Self {}
1073
1074                        #[ink(message)]
1075                        pub fn my_message(&self) {}
1076                    }
1077                }
1078            })
1079            .is_ok()
1080        );
1081    }
1082
1083    #[test]
1084    fn overlap_between_wildcard_selector_and_composed_selector_fails() {
1085        assert_fail(
1086            syn::parse_quote! {
1087                mod my_module {
1088                    #[ink(storage)]
1089                    pub struct MyStorage {}
1090
1091                    impl MyStorage {
1092                        #[ink(constructor)]
1093                        pub fn my_constructor() -> Self {}
1094
1095                        #[ink(message, selector = _, selector = 0xCAFEBABE)]
1096                        pub fn my_message(&self) {}
1097                    }
1098                }
1099            },
1100            "encountered ink! attribute arguments with equal kinds",
1101        );
1102    }
1103
1104    #[test]
1105    fn wildcard_selector_and_one_other_message_with_well_known_selector_works() {
1106        assert!(
1107            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1108                mod my_module {
1109                    #[ink(storage)]
1110                    pub struct MyStorage {}
1111
1112                    impl MyStorage {
1113                        #[ink(constructor)]
1114                        pub fn my_constructor() -> Self {}
1115
1116                        #[ink(message, selector = _)]
1117                        pub fn fallback(&self) {}
1118
1119                        #[ink(message, selector = 0x9BAE9D5E)]
1120                        pub fn wildcard_complement_message(&self) {}
1121                    }
1122                }
1123            })
1124            .is_ok()
1125        );
1126    }
1127
1128    #[test]
1129    fn wildcard_selector_and_one_other_message_with_wildcard_complement_selector_works() {
1130        assert!(
1131            <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1132                mod my_module {
1133                    #[ink(storage)]
1134                    pub struct MyStorage {}
1135
1136                    impl MyStorage {
1137                        #[ink(constructor)]
1138                        pub fn my_constructor() -> Self {}
1139
1140                        #[ink(message, selector = _)]
1141                        pub fn fallback(&self) {}
1142
1143                        #[ink(message, selector = @)]
1144                        pub fn wildcard_complement_message(&self) {}
1145                    }
1146                }
1147            })
1148            .is_ok()
1149        );
1150    }
1151
1152    #[test]
1153    fn wildcard_selector_without_other_message_fails() {
1154        assert_fail(syn::parse_quote! {
1155                mod my_module {
1156                    #[ink(storage)]
1157                    pub struct MyStorage {}
1158
1159                    impl MyStorage {
1160                        #[ink(constructor)]
1161                        pub fn my_constructor() -> Self {}
1162
1163                        #[ink(message, selector = _)]
1164                        pub fn fallback(&self) {}
1165                    }
1166                }
1167            },
1168            "missing definition of another message with TODO in tandem with a wildcard selector"
1169        )
1170    }
1171
1172    #[test]
1173    fn wildcard_selector_and_one_other_message_without_well_known_selector_fails() {
1174        assert_fail(syn::parse_quote! {
1175                mod my_module {
1176                    #[ink(storage)]
1177                    pub struct MyStorage {}
1178
1179                    impl MyStorage {
1180                        #[ink(constructor)]
1181                        pub fn my_constructor() -> Self {}
1182
1183                        #[ink(message, selector = _)]
1184                        pub fn fallback(&self) {}
1185
1186                        #[ink(message)]
1187                        pub fn other_message_without_well_known_selector(&self) {}
1188                    }
1189                }
1190            },
1191"when using a wildcard selector `selector = _` for an ink! message then the other \
1192            message must use the wildcard complement `selector = @`"
1193        );
1194    }
1195
1196    #[test]
1197    fn wildcard_selector_with_two_other_messages() {
1198        assert_fail(
1199            syn::parse_quote! {
1200                mod my_module {
1201                    #[ink(storage)]
1202                    pub struct MyStorage {}
1203
1204                    impl MyStorage {
1205                        #[ink(constructor)]
1206                        pub fn my_constructor() -> Self {}
1207
1208                        #[ink(message, selector = _)]
1209                        pub fn fallback(&self) {}
1210
1211                        #[ink(message, selector = 0x00000000)]
1212                        pub fn wildcard_complement_message(&self) {}
1213
1214                        #[ink(message)]
1215                        pub fn another_message_not_allowed(&self) {}
1216                    }
1217                }
1218            },
1219            "exactly one other message must be defined together with a wildcard selector",
1220        );
1221    }
1222
1223    #[test]
1224    fn wildcard_selector_with_many_other_messages() {
1225        assert_fail(
1226            syn::parse_quote! {
1227                mod my_module {
1228                    #[ink(storage)]
1229                    pub struct MyStorage {}
1230
1231                    impl MyStorage {
1232                        #[ink(constructor)]
1233                        pub fn my_constructor() -> Self {}
1234
1235                        #[ink(message, selector = _)]
1236                        pub fn fallback(&self) {}
1237
1238                        #[ink(message, selector = @)]
1239                        pub fn wildcard_complement(&self) {}
1240
1241                        #[ink(message)]
1242                        pub fn another_message_not_allowed1(&self) {}
1243
1244                        #[ink(message)]
1245                        pub fn another_message_not_allowed2(&self) {}
1246
1247                        #[ink(message)]
1248                        pub fn another_message_not_allowed3(&self) {}
1249                    }
1250                }
1251            },
1252            "exactly one other message must be defined together with a wildcard selector",
1253        );
1254    }
1255
1256    #[test]
1257    fn wildcard_complement_used_without_wildcard_fails() {
1258        assert_fail(
1259            syn::parse_quote! {
1260                mod my_module {
1261                    #[ink(storage)]
1262                    pub struct MyStorage {}
1263
1264                    impl MyStorage {
1265                        #[ink(constructor)]
1266                        pub fn my_constructor() -> Self {}
1267
1268                        #[ink(message, selector = @)]
1269                        pub fn uses_reserved_wildcard_other_message_selector(&self) {}
1270                    }
1271                }
1272            },
1273            "encountered ink! message with wildcard complement `selector = @` but no \
1274            wildcard `selector = _` defined",
1275        )
1276    }
1277
1278    #[test]
1279    fn wildcard_reserved_selector_used_without_wildcard_fails() {
1280        assert_fail(
1281            syn::parse_quote! {
1282                mod my_module {
1283                    #[ink(storage)]
1284                    pub struct MyStorage {}
1285
1286                    impl MyStorage {
1287                        #[ink(constructor)]
1288                        pub fn my_constructor() -> Self {}
1289
1290                        #[ink(message, selector = 0x9BAE9D5E)]
1291                        pub fn uses_reserved_wildcard_other_message_selector(&self) {}
1292                    }
1293                }
1294            },
1295            "encountered ink! message with wildcard complement `selector = @` but no \
1296            wildcard `selector = _` defined",
1297        )
1298    }
1299
1300    #[test]
1301    fn cfg_feature_std_not_allowed() {
1302        let item_mod = syn::parse_quote! {
1303            mod my_module {
1304                #[ink(storage)]
1305                pub struct MyStorage {}
1306
1307                impl MyStorage {
1308                    #[ink(constructor)]
1309                    pub fn my_constructor() -> Self {}
1310
1311                    #[ink(message)]
1312                    #[cfg(feature = "std")]
1313                    pub fn not_allowed(&self) {}
1314                }
1315            }
1316        };
1317        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1318            .map_err(|err| err.to_string());
1319        assert!(res.is_err());
1320        assert!(res
1321            .unwrap_err()
1322            .starts_with("The feature `std` is not allowed in `cfg`."));
1323    }
1324
1325    #[test]
1326    fn cfg_feature_other_than_std_allowed() {
1327        let item_mod = syn::parse_quote! {
1328            mod my_module {
1329                #[ink(storage)]
1330                pub struct MyStorage {}
1331
1332                impl MyStorage {
1333                    #[ink(constructor)]
1334                    pub fn my_constructor() -> Self {}
1335
1336                    #[ink(message)]
1337                    pub fn not_allowed(&self) {}
1338                }
1339            }
1340        };
1341        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1342            .map_err(|err| err.to_string());
1343        assert!(res.is_ok());
1344    }
1345
1346    #[test]
1347    fn cfg_test_allowed() {
1348        let item_mod = syn::parse_quote! {
1349            mod my_module {
1350                #[ink(storage)]
1351                pub struct MyStorage {}
1352
1353                impl MyStorage {
1354                    #[ink(constructor)]
1355                    pub fn my_constructor() -> Self {}
1356
1357                    #[ink(message)]
1358                    #[cfg(test)]
1359                    pub fn not_allowed(&self) {}
1360                }
1361            }
1362        };
1363        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1364            .map_err(|err| err.to_string());
1365        assert!(res.is_ok());
1366    }
1367
1368    #[test]
1369    fn cfg_nested_forbidden_must_be_found() {
1370        let item_mod = syn::parse_quote! {
1371            mod my_module {
1372                #[ink(storage)]
1373                pub struct MyStorage {}
1374
1375                impl MyStorage {
1376                    #[ink(constructor)]
1377                    #[cfg(any(not(target_os = "wasm")))]
1378                    pub fn my_constructor() -> Self {}
1379
1380                    #[ink(message)]
1381                    pub fn not_allowed(&self) {}
1382                }
1383            }
1384        };
1385        let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1386            .map_err(|err| err.to_string());
1387        assert!(res.is_err());
1388        assert!(res
1389            .unwrap_err()
1390            .starts_with("This `cfg` attribute is not allowed."));
1391    }
1392}