1use 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#[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(
148 module_span: Span,
149 items: &[ir::Item],
150 ) -> Result<(), syn::Error> {
151 let found_constructor = items
152 .iter()
153 .filter_map(|item| {
154 match item {
155 ir::Item::Ink(ir::InkItem::ImplBlock(item_impl)) => {
156 Some(item_impl.iter_constructors())
157 }
158 _ => None,
159 }
160 })
161 .any(|mut constructors| constructors.next().is_some());
162 if !found_constructor {
163 return Err(format_err!(module_span, "missing ink! constructor"))
164 }
165 Ok(())
166 }
167
168 fn ensure_no_overlapping_selectors(items: &[ir::Item]) -> Result<(), syn::Error> {
176 let mut messages = <HashMap<ir::Selector, &ir::Message>>::new();
177 let mut constructors = <HashMap<ir::Selector, &ir::Constructor>>::new();
178 for item_impl in items
179 .iter()
180 .filter_map(ir::Item::map_ink_item)
181 .filter_map(ir::InkItem::filter_map_impl_block)
182 {
183 use std::collections::hash_map::Entry;
184 fn compose_error(
186 first_span: Span,
187 second_span: Span,
188 selector: ir::Selector,
189 kind: &str,
190 ) -> syn::Error {
191 format_err!(
192 second_span,
193 "encountered ink! {}s with overlapping selectors (= {:02X?})\n\
194 hint: use #[ink(selector = S:u32)] on the callable or \
195 #[ink(namespace = N:string)] on the implementation block to \
196 disambiguate overlapping selectors.",
197 kind,
198 selector.to_bytes(),
199 )
200 .into_combine(format_err!(
201 first_span,
202 "first ink! {} with overlapping selector here",
203 kind,
204 ))
205 }
206 for message in item_impl.iter_messages() {
207 let selector = message.composed_selector();
208 match messages.entry(selector) {
209 Entry::Occupied(overlap) => {
210 return Err(compose_error(
211 overlap.get().span(),
212 message.callable().span(),
213 selector,
214 "message",
215 ))
216 }
217 Entry::Vacant(vacant) => {
218 vacant.insert(message.callable());
219 }
220 }
221 }
222 for constructor in item_impl.iter_constructors() {
223 let selector = constructor.composed_selector();
224 match constructors.entry(selector) {
225 Entry::Occupied(overlap) => {
226 return Err(compose_error(
227 overlap.get().span(),
228 constructor.callable().span(),
229 selector,
230 "constructor",
231 ))
232 }
233 Entry::Vacant(vacant) => {
234 vacant.insert(constructor.callable());
235 }
236 }
237 }
238 }
239 Ok(())
240 }
241
242 fn ensure_only_allowed_cfgs(items: &[ir::Item]) -> Result<(), syn::Error> {
256 const ERR_HELP: &str = "Allowed in `#[cfg(…)]`:\n\
257 - `test`\n\
258 - `feature` (without `std`)\n\
259 - `any`\n\
260 - `not`\n\
261 - `all`";
262
263 fn verify_attr(a: &syn::Attribute) -> Result<(), syn::Error> {
264 match &a.meta {
265 syn::Meta::List(list) => {
266 if let Some(ident) = list.path.get_ident() {
267 if ident.eq("cfg") {
268 return list.parse_nested_meta(verify_cfg_attrs);
269 }
270 }
271 unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for other `List`");
272 }
273 syn::Meta::Path(_) => {
274 unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for `Path`");
276 }
277 syn::Meta::NameValue(_) => {
278 unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for `NameValue`");
280 }
281 }
282 }
283
284 fn verify_cfg_attrs(meta: syn::meta::ParseNestedMeta) -> Result<(), syn::Error> {
285 if meta.path.is_ident("test") {
286 return Ok(());
287 }
288 if meta.path.is_ident("any")
289 || meta.path.is_ident("all")
290 || meta.path.is_ident("not")
291 {
292 return meta.parse_nested_meta(verify_cfg_attrs);
293 }
294
295 if meta.path.is_ident("feature") {
296 let value = meta.value()?;
297 let value: syn::LitStr = value.parse()?;
298 if value.value().eq("std") {
299 return Err(format_err_spanned!(
300 meta.path,
301 "The feature `std` is not allowed in `cfg`.\n\n{ERR_HELP}"
302 ))
303 }
304 return Ok(());
305 }
306
307 Err(format_err_spanned!(
308 meta.path,
309 "This `cfg` attribute is not allowed.\n\n{ERR_HELP}"
310 ))
311 }
312
313 for item_impl in items
314 .iter()
315 .filter_map(ir::Item::map_ink_item)
316 .filter_map(ir::InkItem::filter_map_impl_block)
317 {
318 for message in item_impl.iter_messages() {
319 for a in message.get_cfg_syn_attrs() {
320 verify_attr(&a)?;
321 }
322 }
323 for constructor in item_impl.iter_constructors() {
324 for a in constructor.get_cfg_syn_attrs() {
325 verify_attr(&a)?;
326 }
327 }
328 }
329 Ok(())
330 }
331
332 fn ensure_valid_wildcard_selector_usage(
338 items: &[ir::Item],
339 ) -> Result<(), syn::Error> {
340 let mut wildcard_selector: Option<&ir::Message> = None;
341 let mut other_messages = Vec::new();
342 for item_impl in items
343 .iter()
344 .filter_map(ir::Item::map_ink_item)
345 .filter_map(ir::InkItem::filter_map_impl_block)
346 {
347 for message in item_impl.iter_messages() {
348 if !message.has_wildcard_selector() {
349 other_messages.push(message);
350 continue
351 }
352 match wildcard_selector {
353 None => wildcard_selector = Some(message.callable()),
354 Some(overlap) => {
355 let err = format_err!(
356 message.callable().span(),
357 "encountered ink! messages with overlapping wildcard selectors",
358 );
359 let overlap_err = format_err!(
360 overlap.span(),
361 "first ink! message with overlapping wildcard selector here",
362 );
363 return Err(err.into_combine(overlap_err))
364 }
365 }
366 }
367
368 if let Some(wildcard) = wildcard_selector {
369 match other_messages.len() as u32 {
370 0 => return Err(format_err!(
371 wildcard.span(),
372 "missing definition of another message with TODO in tandem with a wildcard \
373 selector",
374 )),
375 1 => {
376 if !other_messages[0].callable().has_wildcard_complement_selector() {
377 return Err(format_err!(
378 other_messages[0].callable().span(),
379 "when using a wildcard selector `selector = _` for an ink! message \
380 then the other message must use the wildcard complement `selector = @`"
381 ))
382 }
383 }
384 2.. => {
385 let mut combined = format_err!(
386 wildcard.span(),
387 "exactly one other message must be defined together with a wildcard selector",
388 );
389 for message in &other_messages {
390 if !message.callable().has_wildcard_complement_selector() {
391 combined.combine(
392 format_err!(
393 message.callable().span(),
394 "additional message not permitted together with a wildcard selector",
395 )
396 )
397 }
398 }
399 return Err(combined)
400 }
401 }
402 } else {
403 for message in &other_messages {
404 if message.callable().has_wildcard_complement_selector() {
405 return Err(format_err!(
406 message.callable().span(),
407 "encountered ink! message with wildcard complement `selector = @` but no \
408 wildcard `selector = _` defined"
409 ));
410 }
411 }
412 }
413
414 let mut wildcard_selector: Option<&ir::Constructor> = None;
415 for constructor in item_impl.iter_constructors() {
416 if !constructor.has_wildcard_selector() {
417 continue
418 }
419 match wildcard_selector {
420 None => wildcard_selector = Some(constructor.callable()),
421 Some(overlap) => {
422 return Err(format_err!(
423 constructor.callable().span(),
424 "encountered ink! constructor with overlapping wildcard selectors",
425 )
426 .into_combine(format_err!(
427 overlap.span(),
428 "first ink! constructor with overlapping wildcard selector here",
429 )))
430 }
431 }
432 }
433 }
434 Ok(())
435 }
436}
437
438impl TryFrom<syn::ItemMod> for ItemMod {
439 type Error = syn::Error;
440
441 fn try_from(module: syn::ItemMod) -> Result<Self, Self::Error> {
442 let module_span = module.span();
443 idents_lint::ensure_no_ink_identifiers(&module)?;
444 let (brace, items) = match module.content {
445 Some((brace, items)) => (brace, items),
446 None => {
447 return Err(format_err_spanned!(
448 module,
449 "out-of-line ink! modules are not supported, use `#[ink::contract] mod name {{ ... }}`",
450 ))
451 }
452 };
453 let (ink_attrs, other_attrs) = ir::partition_attributes(module.attrs)?;
454 if !ink_attrs.is_empty() {
455 let mut error = format_err!(
456 module_span,
457 "encountered invalid ink! attributes on ink! module"
458 );
459 for ink_attr in ink_attrs {
460 error.combine(format_err!(
461 ink_attr.span(),
462 "invalid ink! attribute on module"
463 ))
464 }
465 return Err(error)
466 }
467 let items = items
468 .into_iter()
469 .map(<ir::Item as TryFrom<syn::Item>>::try_from)
470 .collect::<Result<Vec<_>, syn::Error>>()?;
471 Self::ensure_storage_struct_quantity(module_span, &items)?;
472 Self::ensure_contains_message(module_span, &items)?;
473 Self::ensure_contains_constructor(module_span, &items)?;
474 Self::ensure_no_overlapping_selectors(&items)?;
475 Self::ensure_valid_wildcard_selector_usage(&items)?;
476 Self::ensure_only_allowed_cfgs(&items)?;
477 Ok(Self {
478 attrs: other_attrs,
479 vis: module.vis,
480 mod_token: module.mod_token,
481 ident: module.ident,
482 brace,
483 items,
484 })
485 }
486}
487
488impl quote::ToTokens for ItemMod {
489 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
492 tokens.append_all(
493 self.attrs
494 .iter()
495 .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
496 );
497 self.vis.to_tokens(tokens);
498 self.mod_token.to_tokens(tokens);
499 self.ident.to_tokens(tokens);
500 self.brace.surround(tokens, |tokens| {
501 tokens.append_all(
502 self.attrs
503 .iter()
504 .filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
505 );
506 tokens.append_all(&self.items);
507 });
508 }
509}
510
511impl ItemMod {
512 pub fn ident(&self) -> &Ident {
514 &self.ident
515 }
516
517 pub fn storage(&self) -> &ir::Storage {
532 let mut iter = IterInkItems::new(self)
533 .filter_map(|ink_item| ink_item.filter_map_storage_item());
534 let storage = iter
535 .next()
536 .expect("encountered ink! module without a storage struct");
537 assert!(
538 iter.next().is_none(),
539 "encountered multiple storage structs in ink! module"
540 );
541 storage
542 }
543
544 pub fn items(&self) -> &[ir::Item] {
547 self.items.as_slice()
548 }
549
550 pub fn impls(&self) -> IterItemImpls {
616 IterItemImpls::new(self)
617 }
618
619 pub fn events(&self) -> IterEvents {
621 IterEvents::new(self)
622 }
623
624 pub fn attrs(&self) -> &[syn::Attribute] {
626 &self.attrs
627 }
628
629 pub fn vis(&self) -> &syn::Visibility {
631 &self.vis
632 }
633}
634
635pub struct IterInkItems<'a> {
637 items_iter: core::slice::Iter<'a, ir::Item>,
638}
639
640impl<'a> IterInkItems<'a> {
641 fn new(ink_module: &'a ItemMod) -> Self {
643 Self {
644 items_iter: ink_module.items.iter(),
645 }
646 }
647}
648
649impl<'a> Iterator for IterInkItems<'a> {
650 type Item = &'a ir::InkItem;
651
652 fn next(&mut self) -> Option<Self::Item> {
653 'repeat: loop {
654 match self.items_iter.next() {
655 None => return None,
656 Some(item) => {
657 if let Some(event) = item.map_ink_item() {
658 return Some(event)
659 }
660 continue 'repeat
661 }
662 }
663 }
664 }
665}
666
667pub struct IterEvents<'a> {
670 items_iter: IterInkItems<'a>,
671}
672
673impl<'a> IterEvents<'a> {
674 fn new(ink_module: &'a ItemMod) -> Self {
676 Self {
677 items_iter: IterInkItems::new(ink_module),
678 }
679 }
680}
681
682impl<'a> Iterator for IterEvents<'a> {
683 type Item = &'a ir::Event;
684
685 fn next(&mut self) -> Option<Self::Item> {
686 'repeat: loop {
687 match self.items_iter.next() {
688 None => return None,
689 Some(ink_item) => {
690 if let Some(event) = ink_item.filter_map_event_item() {
691 return Some(event)
692 }
693 continue 'repeat
694 }
695 }
696 }
697 }
698}
699
700pub struct IterItemImpls<'a> {
703 items_iter: IterInkItems<'a>,
704}
705
706impl<'a> IterItemImpls<'a> {
707 fn new(ink_module: &'a ItemMod) -> Self {
709 Self {
710 items_iter: IterInkItems::new(ink_module),
711 }
712 }
713}
714
715impl<'a> Iterator for IterItemImpls<'a> {
716 type Item = &'a ir::ItemImpl;
717
718 fn next(&mut self) -> Option<Self::Item> {
719 'repeat: loop {
720 match self.items_iter.next() {
721 None => return None,
722 Some(ink_item) => {
723 if let Some(event) = ink_item.filter_map_impl_block() {
724 return Some(event)
725 }
726 continue 'repeat
727 }
728 }
729 }
730 }
731}
732
733#[cfg(test)]
734mod tests {
735 use crate as ir;
736
737 #[test]
738 fn item_mod_try_from_works() {
739 let item_mods: Vec<syn::ItemMod> = vec![
740 syn::parse_quote! {
741 mod minimal {
742 #[ink(storage)]
743 pub struct Minimal {}
744
745 impl Minimal {
746 #[ink(constructor)]
747 pub fn new() -> Self {}
748 #[ink(message)]
749 pub fn minimal_message(&self) {}
750 }
751 }
752 },
753 syn::parse_quote! {
754 mod flipper {
755 #[ink(storage)]
756 pub struct Flipper {
757 value: bool,
758 }
759
760 impl Default for Flipper {
761 #[ink(constructor)]
762 fn default() -> Self {
763 Self { value: false }
764 }
765 }
766
767 impl Flipper {
768 #[ink(message)]
769 pub fn flip(&mut self) {
770 self.value = !self.value
771 }
772
773 #[ink(message)]
774 pub fn get(&self) -> bool {
775 self.value
776 }
777 }
778 }
779 },
780 ];
781 for item_mod in item_mods {
782 assert!(<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod).is_ok())
783 }
784 }
785
786 fn assert_fail(item_mod: syn::ItemMod, expected_err: &str) {
787 assert_eq!(
788 <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
789 .map_err(|err| err.to_string()),
790 Err(expected_err.to_string()),
791 );
792 }
793
794 #[test]
795 fn missing_storage_struct_fails() {
796 assert_fail(
797 syn::parse_quote! {
798 mod my_module {
799 impl MyStorage {
800 #[ink(constructor)]
801 pub fn my_constructor() -> Self {}
802 #[ink(message)]
803 pub fn my_message(&self) {}
804 }
805 }
806 },
807 "missing ink! storage struct",
808 )
809 }
810
811 #[test]
812 fn multiple_storage_struct_fails() {
813 assert_fail(
814 syn::parse_quote! {
815 mod my_module {
816 #[ink(storage)]
817 pub struct MyFirstStorage {}
818 #[ink(storage)]
819 pub struct MySecondStorage {}
820 impl MyFirstStorage {
821 #[ink(constructor)]
822 pub fn my_constructor() -> Self {}
823 #[ink(message)]
824 pub fn my_message(&self) {}
825 }
826 }
827 },
828 "encountered multiple ink! storage structs, expected exactly one",
829 )
830 }
831
832 #[test]
833 fn missing_constructor_fails() {
834 assert_fail(
835 syn::parse_quote! {
836 mod my_module {
837 #[ink(storage)]
838 pub struct MyStorage {}
839
840 impl MyStorage {
841 #[ink(message)]
842 pub fn my_message(&self) {}
843 }
844 }
845 },
846 "missing ink! constructor",
847 )
848 }
849
850 #[test]
851 fn missing_message_fails() {
852 assert_fail(
853 syn::parse_quote! {
854 mod my_module {
855 #[ink(storage)]
856 pub struct MyStorage {}
857
858 impl MyStorage {
859 #[ink(constructor)]
860 pub fn my_constructor() -> Self {}
861 }
862 }
863 },
864 "missing ink! message",
865 )
866 }
867
868 #[test]
869 fn invalid_out_of_line_module_fails() {
870 assert_fail(
871 syn::parse_quote! {
872 mod my_module;
873 },
874 "out-of-line ink! modules are not supported, use `#[ink::contract] mod name { ... }`",
875 )
876 }
877
878 #[test]
879 fn conflicting_attributes_fails() {
880 assert_fail(
881 syn::parse_quote! {
882 #[ink(namespace = "my_namespace")]
883 mod my_module {
884 #[ink(storage)]
885 pub struct MyStorage {}
886 impl MyStorage {
887 #[ink(constructor)]
888 pub fn my_constructor() -> Self {}
889 #[ink(message)]
890 pub fn my_message(&self) {}
891 }
892 }
893 },
894 "encountered invalid ink! attributes on ink! module",
895 )
896 }
897
898 #[test]
899 fn overlapping_messages_fails() {
900 assert_fail(
901 syn::parse_quote! {
902 mod my_module {
903 #[ink(storage)]
904 pub struct MyStorage {}
905
906 impl MyStorage {
907 #[ink(constructor)]
908 pub fn my_constructor() -> Self {}
909
910 #[ink(message, selector = 0xDEADBEEF)]
911 pub fn my_message_1(&self) {}
912 }
913
914 impl MyStorage {
915 #[ink(message, selector = 0xDEADBEEF)]
916 pub fn my_message_2(&self) {}
917 }
918 }
919 },
920 "encountered ink! messages with overlapping selectors (= [DE, AD, BE, EF])\n\
921 hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
922 on the implementation block to disambiguate overlapping selectors.",
923 );
924 }
925
926 #[test]
927 fn overlapping_constructors_fails() {
928 assert_fail(
929 syn::parse_quote! {
930 mod my_module {
931 #[ink(storage)]
932 pub struct MyStorage {}
933
934 impl MyStorage {
935 #[ink(constructor, selector = 0xDEADBEEF)]
936 pub fn my_constructor_1() -> Self {}
937
938 #[ink(message)]
939 pub fn my_message_1(&self) {}
940 }
941
942 impl MyStorage {
943 #[ink(constructor, selector = 0xDEADBEEF)]
944 pub fn my_constructor_2() -> Self {}
945 }
946 }
947 },
948 "encountered ink! constructors with overlapping selectors (= [DE, AD, BE, EF])\n\
949 hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
950 on the implementation block to disambiguate overlapping selectors.",
951 );
952 }
953
954 #[test]
955 fn overlapping_trait_impls_fails() {
956 assert_fail(
957 syn::parse_quote! {
958 mod my_module {
959 #[ink(storage)]
960 pub struct MyStorage {}
961
962 impl first::MyTrait for MyStorage {
963 #[ink(constructor)]
964 fn my_constructor() -> Self {}
965
966 #[ink(message)]
967 fn my_message(&self) {}
968 }
969
970 impl second::MyTrait for MyStorage {
971 #[ink(message)]
972 fn my_message(&self) {}
973 }
974 }
975 },
976 "encountered ink! messages with overlapping selectors (= [04, C4, 94, 46])\n\
977 hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
978 on the implementation block to disambiguate overlapping selectors.",
979 );
980 }
981
982 #[test]
983 fn allow_overlap_between_messages_and_constructors() {
984 assert!(
985 <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
986 mod my_module {
987 #[ink(storage)]
988 pub struct MyStorage {}
989
990 impl MyStorage {
991 #[ink(constructor, selector = 0xDEADBEEF)]
992 pub fn my_constructor() -> Self {}
993
994 #[ink(message, selector = 0xDEADBEEF)]
995 pub fn my_message(&self) {}
996 }
997 }
998 })
999 .is_ok()
1000 );
1001 }
1002
1003 #[test]
1004 fn overlapping_wildcard_selectors_fails() {
1005 assert_fail(
1006 syn::parse_quote! {
1007 mod my_module {
1008 #[ink(storage)]
1009 pub struct MyStorage {}
1010
1011 impl MyStorage {
1012 #[ink(constructor)]
1013 pub fn my_constructor() -> Self {}
1014
1015 #[ink(message, selector = _)]
1016 pub fn my_message1(&self) {}
1017
1018 #[ink(message, selector = _)]
1019 pub fn my_message2(&self) {}
1020 }
1021 }
1022 },
1023 "encountered ink! messages with overlapping wildcard selectors",
1024 );
1025 }
1026
1027 #[test]
1028 fn wildcard_selector_on_constructor_works() {
1029 assert!(
1030 <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1031 mod my_module {
1032 #[ink(storage)]
1033 pub struct MyStorage {}
1034
1035 impl MyStorage {
1036 #[ink(constructor, selector = _)]
1037 pub fn my_constructor() -> Self {}
1038
1039 #[ink(message)]
1040 pub fn my_message(&self) {}
1041 }
1042 }
1043 })
1044 .is_ok()
1045 );
1046 }
1047
1048 #[test]
1049 fn overlap_between_wildcard_selector_and_composed_selector_fails() {
1050 assert_fail(
1051 syn::parse_quote! {
1052 mod my_module {
1053 #[ink(storage)]
1054 pub struct MyStorage {}
1055
1056 impl MyStorage {
1057 #[ink(constructor)]
1058 pub fn my_constructor() -> Self {}
1059
1060 #[ink(message, selector = _, selector = 0xCAFEBABE)]
1061 pub fn my_message(&self) {}
1062 }
1063 }
1064 },
1065 "encountered ink! attribute arguments with equal kinds",
1066 );
1067 }
1068
1069 #[test]
1070 fn wildcard_selector_and_one_other_message_with_well_known_selector_works() {
1071 assert!(
1072 <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1073 mod my_module {
1074 #[ink(storage)]
1075 pub struct MyStorage {}
1076
1077 impl MyStorage {
1078 #[ink(constructor)]
1079 pub fn my_constructor() -> Self {}
1080
1081 #[ink(message, selector = _)]
1082 pub fn fallback(&self) {}
1083
1084 #[ink(message, selector = 0x9BAE9D5E)]
1085 pub fn wildcard_complement_message(&self) {}
1086 }
1087 }
1088 })
1089 .is_ok()
1090 );
1091 }
1092
1093 #[test]
1094 fn wildcard_selector_and_one_other_message_with_wildcard_complement_selector_works() {
1095 assert!(
1096 <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
1097 mod my_module {
1098 #[ink(storage)]
1099 pub struct MyStorage {}
1100
1101 impl MyStorage {
1102 #[ink(constructor)]
1103 pub fn my_constructor() -> Self {}
1104
1105 #[ink(message, selector = _)]
1106 pub fn fallback(&self) {}
1107
1108 #[ink(message, selector = @)]
1109 pub fn wildcard_complement_message(&self) {}
1110 }
1111 }
1112 })
1113 .is_ok()
1114 );
1115 }
1116
1117 #[test]
1118 fn wildcard_selector_without_other_message_fails() {
1119 assert_fail(syn::parse_quote! {
1120 mod my_module {
1121 #[ink(storage)]
1122 pub struct MyStorage {}
1123
1124 impl MyStorage {
1125 #[ink(constructor)]
1126 pub fn my_constructor() -> Self {}
1127
1128 #[ink(message, selector = _)]
1129 pub fn fallback(&self) {}
1130 }
1131 }
1132 },
1133 "missing definition of another message with TODO in tandem with a wildcard selector"
1134 )
1135 }
1136
1137 #[test]
1138 fn wildcard_selector_and_one_other_message_without_well_known_selector_fails() {
1139 assert_fail(syn::parse_quote! {
1140 mod my_module {
1141 #[ink(storage)]
1142 pub struct MyStorage {}
1143
1144 impl MyStorage {
1145 #[ink(constructor)]
1146 pub fn my_constructor() -> Self {}
1147
1148 #[ink(message, selector = _)]
1149 pub fn fallback(&self) {}
1150
1151 #[ink(message)]
1152 pub fn other_message_without_well_known_selector(&self) {}
1153 }
1154 }
1155 },
1156"when using a wildcard selector `selector = _` for an ink! message then the other \
1157 message must use the wildcard complement `selector = @`"
1158 );
1159 }
1160
1161 #[test]
1162 fn wildcard_selector_with_two_other_messages() {
1163 assert_fail(
1164 syn::parse_quote! {
1165 mod my_module {
1166 #[ink(storage)]
1167 pub struct MyStorage {}
1168
1169 impl MyStorage {
1170 #[ink(constructor)]
1171 pub fn my_constructor() -> Self {}
1172
1173 #[ink(message, selector = _)]
1174 pub fn fallback(&self) {}
1175
1176 #[ink(message, selector = 0x00000000)]
1177 pub fn wildcard_complement_message(&self) {}
1178
1179 #[ink(message)]
1180 pub fn another_message_not_allowed(&self) {}
1181 }
1182 }
1183 },
1184 "exactly one other message must be defined together with a wildcard selector",
1185 );
1186 }
1187
1188 #[test]
1189 fn wildcard_selector_with_many_other_messages() {
1190 assert_fail(
1191 syn::parse_quote! {
1192 mod my_module {
1193 #[ink(storage)]
1194 pub struct MyStorage {}
1195
1196 impl MyStorage {
1197 #[ink(constructor)]
1198 pub fn my_constructor() -> Self {}
1199
1200 #[ink(message, selector = _)]
1201 pub fn fallback(&self) {}
1202
1203 #[ink(message, selector = @)]
1204 pub fn wildcard_complement(&self) {}
1205
1206 #[ink(message)]
1207 pub fn another_message_not_allowed1(&self) {}
1208
1209 #[ink(message)]
1210 pub fn another_message_not_allowed2(&self) {}
1211
1212 #[ink(message)]
1213 pub fn another_message_not_allowed3(&self) {}
1214 }
1215 }
1216 },
1217 "exactly one other message must be defined together with a wildcard selector",
1218 );
1219 }
1220
1221 #[test]
1222 fn wildcard_complement_used_without_wildcard_fails() {
1223 assert_fail(
1224 syn::parse_quote! {
1225 mod my_module {
1226 #[ink(storage)]
1227 pub struct MyStorage {}
1228
1229 impl MyStorage {
1230 #[ink(constructor)]
1231 pub fn my_constructor() -> Self {}
1232
1233 #[ink(message, selector = @)]
1234 pub fn uses_reserved_wildcard_other_message_selector(&self) {}
1235 }
1236 }
1237 },
1238 "encountered ink! message with wildcard complement `selector = @` but no \
1239 wildcard `selector = _` defined",
1240 )
1241 }
1242
1243 #[test]
1244 fn wildcard_reserved_selector_used_without_wildcard_fails() {
1245 assert_fail(
1246 syn::parse_quote! {
1247 mod my_module {
1248 #[ink(storage)]
1249 pub struct MyStorage {}
1250
1251 impl MyStorage {
1252 #[ink(constructor)]
1253 pub fn my_constructor() -> Self {}
1254
1255 #[ink(message, selector = 0x9BAE9D5E)]
1256 pub fn uses_reserved_wildcard_other_message_selector(&self) {}
1257 }
1258 }
1259 },
1260 "encountered ink! message with wildcard complement `selector = @` but no \
1261 wildcard `selector = _` defined",
1262 )
1263 }
1264
1265 #[test]
1266 fn cfg_feature_std_not_allowed() {
1267 let item_mod = syn::parse_quote! {
1268 mod my_module {
1269 #[ink(storage)]
1270 pub struct MyStorage {}
1271
1272 impl MyStorage {
1273 #[ink(constructor)]
1274 pub fn my_constructor() -> Self {}
1275
1276 #[ink(message)]
1277 #[cfg(feature = "std")]
1278 pub fn not_allowed(&self) {}
1279 }
1280 }
1281 };
1282 let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1283 .map_err(|err| err.to_string());
1284 assert!(res.is_err());
1285 assert!(res
1286 .unwrap_err()
1287 .starts_with("The feature `std` is not allowed in `cfg`."));
1288 }
1289
1290 #[test]
1291 fn cfg_feature_other_than_std_allowed() {
1292 let item_mod = syn::parse_quote! {
1293 mod my_module {
1294 #[ink(storage)]
1295 pub struct MyStorage {}
1296
1297 impl MyStorage {
1298 #[ink(constructor)]
1299 pub fn my_constructor() -> Self {}
1300
1301 #[ink(message)]
1302 pub fn not_allowed(&self) {}
1303 }
1304 }
1305 };
1306 let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1307 .map_err(|err| err.to_string());
1308 assert!(res.is_ok());
1309 }
1310
1311 #[test]
1312 fn cfg_test_allowed() {
1313 let item_mod = syn::parse_quote! {
1314 mod my_module {
1315 #[ink(storage)]
1316 pub struct MyStorage {}
1317
1318 impl MyStorage {
1319 #[ink(constructor)]
1320 pub fn my_constructor() -> Self {}
1321
1322 #[ink(message)]
1323 #[cfg(test)]
1324 pub fn not_allowed(&self) {}
1325 }
1326 }
1327 };
1328 let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1329 .map_err(|err| err.to_string());
1330 assert!(res.is_ok());
1331 }
1332
1333 #[test]
1334 fn cfg_nested_forbidden_must_be_found() {
1335 let item_mod = syn::parse_quote! {
1336 mod my_module {
1337 #[ink(storage)]
1338 pub struct MyStorage {}
1339
1340 impl MyStorage {
1341 #[ink(constructor)]
1342 #[cfg(any(not(target_os = "wasm")))]
1343 pub fn my_constructor() -> Self {}
1344
1345 #[ink(message)]
1346 pub fn not_allowed(&self) {}
1347 }
1348 }
1349 };
1350 let res = <ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
1351 .map_err(|err| err.to_string());
1352 assert!(res.is_err());
1353 assert!(res
1354 .unwrap_err()
1355 .starts_with("This `cfg` attribute is not allowed."));
1356 }
1357}