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(
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 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 unreachable!("`verify_attr` can only be called for `#[cfg(…)]`, not for `Path`");
311 }
312 syn::Meta::NameValue(_) => {
313 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 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 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 pub fn ident(&self) -> &Ident {
549 &self.ident
550 }
551
552 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 pub fn items(&self) -> &[ir::Item] {
582 self.items.as_slice()
583 }
584
585 pub fn impls(&self) -> IterItemImpls<'_> {
651 IterItemImpls::new(self)
652 }
653
654 pub fn events(&self) -> IterEvents<'_> {
656 IterEvents::new(self)
657 }
658
659 pub fn attrs(&self) -> &[syn::Attribute] {
661 &self.attrs
662 }
663
664 pub fn vis(&self) -> &syn::Visibility {
666 &self.vis
667 }
668}
669
670pub struct IterInkItems<'a> {
672 items_iter: core::slice::Iter<'a, ir::Item>,
673}
674
675impl<'a> IterInkItems<'a> {
676 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
702pub struct IterEvents<'a> {
705 items_iter: IterInkItems<'a>,
706}
707
708impl<'a> IterEvents<'a> {
709 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
735pub struct IterItemImpls<'a> {
738 items_iter: IterInkItems<'a>,
739}
740
741impl<'a> IterItemImpls<'a> {
742 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}