1use 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#[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 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 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 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 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 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 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 unreachable!(
313 "`verify_attr` can only be called for `#[cfg(…)]`, not for `Path`"
314 );
315 }
316 syn::Meta::NameValue(_) => {
317 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 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 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 pub fn ident(&self) -> &Ident {
560 &self.ident
561 }
562
563 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 pub fn items(&self) -> &[ir::Item] {
593 self.items.as_slice()
594 }
595
596 pub fn impls(&self) -> IterItemImpls<'_> {
662 IterItemImpls::new(self)
663 }
664
665 pub fn events(&self) -> IterEvents<'_> {
667 IterEvents::new(self)
668 }
669
670 pub fn attrs(&self) -> &[syn::Attribute] {
672 &self.attrs
673 }
674
675 pub fn vis(&self) -> &syn::Visibility {
677 &self.vis
678 }
679}
680
681pub struct IterInkItems<'a> {
683 items_iter: core::slice::Iter<'a, ir::Item>,
684}
685
686impl<'a> IterInkItems<'a> {
687 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
713pub struct IterEvents<'a> {
716 items_iter: IterInkItems<'a>,
717}
718
719impl<'a> IterEvents<'a> {
720 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
746pub struct IterItemImpls<'a> {
749 items_iter: IterInkItems<'a>,
750}
751
752impl<'a> IterItemImpls<'a> {
753 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}