1use core::result::Result;
16use std::collections::HashMap;
17
18use ink_prelude::IIP2_WILDCARD_COMPLEMENT_SELECTOR;
19use proc_macro2::{
20 Span,
21 TokenStream as TokenStream2,
22};
23use quote::ToTokens;
24use syn::{
25 Token,
26 parse::{
27 Parse,
28 ParseStream,
29 },
30 punctuated::Punctuated,
31 spanned::Spanned,
32};
33
34use crate::{
35 ast,
36 error::ExtError as _,
37 ir,
38 ir::{
39 Selector,
40 event::SignatureTopic,
41 },
42 utils::extract_name_override,
43};
44
45pub trait IsDocAttribute {
47 fn is_doc_attribute(&self) -> bool;
49
50 fn extract_docs(&self) -> Option<String>;
52}
53
54impl IsDocAttribute for syn::Attribute {
55 fn is_doc_attribute(&self) -> bool {
56 self.path().is_ident("doc")
57 }
58
59 fn extract_docs(&self) -> Option<String> {
60 if !self.is_doc_attribute() {
61 return None;
62 }
63 match &self.meta {
64 syn::Meta::NameValue(nv) => {
65 if let syn::Expr::Lit(l) = &nv.value
66 && let syn::Lit::Str(s) = &l.lit
67 {
68 return Some(s.value());
69 }
70 }
71 _ => return None,
72 }
73 None
74 }
75}
76
77#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Eq)]
80pub enum Attribute {
81 Ink(InkAttribute),
83 Other(syn::Attribute),
88}
89
90pub trait Attrs {
92 fn attrs(&self) -> &[syn::Attribute];
94}
95
96impl Attrs for syn::ImplItem {
97 fn attrs(&self) -> &[syn::Attribute] {
98 match self {
99 syn::ImplItem::Const(item) => &item.attrs,
100 syn::ImplItem::Fn(item) => &item.attrs,
101 syn::ImplItem::Type(item) => &item.attrs,
102 syn::ImplItem::Macro(item) => &item.attrs,
103 _ => &[],
104 }
105 }
106}
107
108impl Attrs for syn::Item {
109 fn attrs(&self) -> &[syn::Attribute] {
110 use syn::Item;
111 match self {
112 Item::Const(syn::ItemConst { attrs, .. })
113 | Item::Enum(syn::ItemEnum { attrs, .. })
114 | Item::ExternCrate(syn::ItemExternCrate { attrs, .. })
115 | Item::Fn(syn::ItemFn { attrs, .. })
116 | Item::ForeignMod(syn::ItemForeignMod { attrs, .. })
117 | Item::Impl(syn::ItemImpl { attrs, .. })
118 | Item::Macro(syn::ItemMacro { attrs, .. })
119 | Item::Mod(syn::ItemMod { attrs, .. })
120 | Item::Static(syn::ItemStatic { attrs, .. })
121 | Item::Struct(syn::ItemStruct { attrs, .. })
122 | Item::Trait(syn::ItemTrait { attrs, .. })
123 | Item::TraitAlias(syn::ItemTraitAlias { attrs, .. })
124 | Item::Type(syn::ItemType { attrs, .. })
125 | Item::Union(syn::ItemUnion { attrs, .. })
126 | Item::Use(syn::ItemUse { attrs, .. }) => attrs,
127 _ => &[],
128 }
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Hash)]
151pub struct InkAttribute {
152 args: Vec<AttributeFrag>,
154}
155
156impl ToTokens for InkAttribute {
157 fn to_tokens(&self, tokens: &mut TokenStream2) {
158 for arg in &self.args {
159 arg.to_tokens(tokens)
160 }
161 }
162}
163
164impl InkAttribute {
165 pub fn ensure_first(&self, expected: &AttributeArgKind) -> Result<(), syn::Error> {
171 if &self.first().arg.kind() != expected {
172 return Err(format_err!(
173 self.span(),
174 "unexpected first ink! attribute argument",
175 ));
176 }
177 Ok(())
178 }
179
180 fn ensure_no_duplicate_args<'a, A>(args: A) -> Result<(), syn::Error>
187 where
188 A: IntoIterator<Item = &'a ir::AttributeFrag>,
189 {
190 use crate::error::ExtError as _;
191 use std::collections::HashSet;
192 let mut seen: HashSet<&AttributeFrag> = HashSet::new();
193 let mut seen2: HashMap<AttributeArgKind, Span> = HashMap::new();
194 for arg in args.into_iter() {
195 if let Some(seen) = seen.get(arg) {
196 return Err(format_err!(
197 arg.span(),
198 "encountered duplicate ink! attribute arguments"
199 )
200 .into_combine(format_err!(
201 seen.span(),
202 "first equal ink! attribute argument here"
203 )));
204 }
205 if let Some(seen) = seen2.get(&arg.kind().kind()) {
206 return Err(format_err!(
207 arg.span(),
208 "encountered ink! attribute arguments with equal kinds"
209 )
210 .into_combine(format_err!(
211 *seen,
212 "first equal ink! attribute argument with equal kind here"
213 )));
214 }
215 seen.insert(arg);
216 seen2.insert(arg.kind().kind(), arg.span());
217 }
218 Ok(())
219 }
220
221 pub fn from_expanded<A>(attrs: A) -> Result<Self, syn::Error>
234 where
235 A: IntoIterator<Item = Self>,
236 {
237 let args = attrs
238 .into_iter()
239 .flat_map(|attr| attr.args)
240 .collect::<Vec<_>>();
241 if args.is_empty() {
242 return Err(format_err!(
243 Span::call_site(),
244 "encountered unexpected empty expanded ink! attribute arguments",
245 ));
246 }
247 Self::ensure_no_duplicate_args(&args)?;
248 Ok(Self { args })
249 }
250
251 pub fn first(&self) -> &AttributeFrag {
253 self.args
254 .first()
255 .expect("encountered invalid empty ink! attribute list")
256 }
257
258 pub fn args(&self) -> ::core::slice::Iter<'_, AttributeFrag> {
264 self.args.iter()
265 }
266
267 pub fn namespace(&self) -> Option<ir::Namespace> {
269 self.args().find_map(|arg| {
270 if let ir::AttributeArg::Namespace(namespace) = arg.kind() {
271 return Some(namespace.clone());
272 }
273 None
274 })
275 }
276
277 pub fn selector(&self) -> Option<SelectorOrWildcard> {
279 self.args().find_map(|arg| {
280 if let ir::AttributeArg::Selector(selector) = arg.kind() {
281 return Some(*selector);
282 }
283 None
284 })
285 }
286
287 pub fn signature_topic(&self) -> Option<SignatureTopic> {
289 self.args().find_map(|arg| {
290 if let ir::AttributeArg::SignatureTopic(topic) = arg.kind() {
291 return Some(*topic);
292 }
293 None
294 })
295 }
296
297 pub fn is_payable(&self) -> bool {
299 self.args()
300 .any(|arg| matches!(arg.kind(), AttributeArg::Payable))
301 }
302
303 pub fn is_default(&self) -> bool {
305 self.args()
306 .any(|arg| matches!(arg.kind(), AttributeArg::Default))
307 }
308
309 pub fn has_wildcard_selector(&self) -> bool {
311 self.args().any(|arg| {
312 matches!(
313 arg.kind(),
314 AttributeArg::Selector(SelectorOrWildcard::Wildcard)
315 )
316 })
317 }
318
319 pub fn is_anonymous(&self) -> bool {
321 self.args()
322 .any(|arg| matches!(arg.kind(), AttributeArg::Anonymous))
323 }
324
325 pub fn name(&self) -> Option<String> {
327 self.args().find_map(|arg| {
328 match arg.kind() {
329 AttributeArg::Name(name) => Some(name.clone()),
330 _ => None,
331 }
332 })
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub struct AttributeFrag {
339 ast: ast::Meta,
340 arg: AttributeArg,
341}
342
343impl AttributeFrag {
344 pub fn kind(&self) -> &AttributeArg {
346 &self.arg
347 }
348}
349
350impl ToTokens for AttributeFrag {
351 fn to_tokens(&self, tokens: &mut TokenStream2) {
352 self.ast.to_tokens(tokens)
353 }
354}
355
356#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
358pub enum AttributeArgKind {
359 Storage,
361 Event,
363 Anonymous,
365 Message,
367 Constructor,
369 Payable,
371 Default,
373 Selector,
376 SignatureTopicArg,
379 Namespace,
381 Implementation,
383 Name,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
389pub enum AttributeArg {
390 Storage,
395 Event,
399 Anonymous,
407 Message,
412 Constructor,
417 Payable,
422 Default,
425 Selector(SelectorOrWildcard),
432 SignatureTopic(SignatureTopic),
435 Namespace(Namespace),
440 Implementation,
452 Name(String),
462}
463
464impl core::fmt::Display for AttributeArgKind {
465 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
466 match self {
467 Self::Storage => write!(f, "storage"),
468 Self::Event => write!(f, "event"),
469 Self::Anonymous => write!(f, "anonymous"),
470 Self::Message => write!(f, "message"),
471 Self::Constructor => write!(f, "constructor"),
472 Self::Payable => write!(f, "payable"),
473 Self::Selector => {
474 write!(f, "selector = S:[u8; 4] || _")
475 }
476 Self::SignatureTopicArg => {
477 write!(f, "signature_topic = S:[u8; 32]")
478 }
479 Self::Namespace => {
480 write!(f, "namespace = N:string")
481 }
482 Self::Implementation => write!(f, "impl"),
483 Self::Default => write!(f, "default"),
484 Self::Name => write!(f, "name = N:string"),
485 }
486 }
487}
488
489impl AttributeArg {
490 pub fn kind(&self) -> AttributeArgKind {
492 match self {
493 Self::Storage => AttributeArgKind::Storage,
494 Self::Event => AttributeArgKind::Event,
495 Self::Anonymous => AttributeArgKind::Anonymous,
496 Self::Message => AttributeArgKind::Message,
497 Self::Constructor => AttributeArgKind::Constructor,
498 Self::Payable => AttributeArgKind::Payable,
499 Self::Selector(_) => AttributeArgKind::Selector,
500 Self::SignatureTopic(_) => AttributeArgKind::SignatureTopicArg,
501 Self::Namespace(_) => AttributeArgKind::Namespace,
502 Self::Implementation => AttributeArgKind::Implementation,
503 Self::Default => AttributeArgKind::Default,
504 Self::Name(_) => AttributeArgKind::Name,
505 }
506 }
507}
508
509impl core::fmt::Display for AttributeArg {
510 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
511 match self {
512 Self::Storage => write!(f, "storage"),
513 Self::Event => write!(f, "event"),
514 Self::Anonymous => write!(f, "anonymous"),
515 Self::Message => write!(f, "message"),
516 Self::Constructor => write!(f, "constructor"),
517 Self::Payable => write!(f, "payable"),
518 Self::Selector(selector) => core::fmt::Display::fmt(&selector, f),
519 Self::SignatureTopic(topic) => {
520 write!(f, "signature_topic = {topic}")
521 }
522 Self::Namespace(namespace) => {
523 write!(f, "namespace = {:?}", namespace.as_bytes())
524 }
525 Self::Implementation => write!(f, "impl"),
526 Self::Default => write!(f, "default"),
527 Self::Name(name) => write!(f, "name = {name:?}"),
528 }
529 }
530}
531
532#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
534pub enum SelectorOrWildcard {
535 Wildcard,
538 UserProvided(Selector),
540}
541
542impl SelectorOrWildcard {
543 fn selector(bytes: [u8; 4]) -> Self {
545 SelectorOrWildcard::UserProvided(Selector::from(bytes))
546 }
547
548 pub fn wildcard_complement() -> Self {
550 Self::selector(IIP2_WILDCARD_COMPLEMENT_SELECTOR)
551 }
552}
553
554impl TryFrom<&ast::MetaValue> for SelectorOrWildcard {
555 type Error = syn::Error;
556
557 fn try_from(value: &ast::MetaValue) -> Result<Self, Self::Error> {
558 match value {
559 ast::MetaValue::Lit(lit) => {
560 if let syn::Lit::Str(_) = lit {
561 return Err(format_err_spanned!(
562 lit,
563 "#[ink(selector = ..)] attributes with string inputs are deprecated. \
564 use an integer instead, e.g. #[ink(selector = 1)] or #[ink(selector = 0xC0DECAFE)]."
565 ));
566 }
567 if let syn::Lit::Int(lit_int) = lit {
568 let selector_u32 = lit_int.base10_parse::<u32>()
569 .map_err(|error| {
570 format_err_spanned!(
571 lit_int,
572 "selector value out of range. selector must be a valid `u32` integer: {}",
573 error
574 )
575 })?;
576 let selector = Selector::from(selector_u32.to_be_bytes());
577 return Ok(SelectorOrWildcard::UserProvided(selector))
578 }
579 Err(format_err_spanned!(
580 value,
581 "expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"
582 ))
583 }
584 ast::MetaValue::Symbol(symbol) => {
585 match symbol {
586 ast::Symbol::Underscore(_) => Ok(SelectorOrWildcard::Wildcard),
587 ast::Symbol::AtSign(_) => {
588 Ok(SelectorOrWildcard::wildcard_complement())
589 }
590 }
591 }
592 ast::MetaValue::Path(path) => {
593 Err(format_err_spanned!(
594 path,
595 "unexpected path for `selector` argument, expected a 4-digit hexcode or one of \
596 the wildcard symbols: `_` or `@`"
597 ))
598 }
599 }
600 }
601}
602
603impl core::fmt::Display for SelectorOrWildcard {
604 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
605 match self {
606 Self::UserProvided(selector) => core::fmt::Debug::fmt(&selector, f),
607 Self::Wildcard => write!(f, "_"),
608 }
609 }
610}
611
612#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
614pub struct Namespace {
615 bytes: Vec<u8>,
617}
618
619impl TryFrom<&ast::MetaValue> for Namespace {
620 type Error = syn::Error;
621
622 fn try_from(value: &ast::MetaValue) -> Result<Self, Self::Error> {
623 if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = value {
624 let argument = lit_str.value();
625 syn::parse_str::<syn::Ident>(&argument).map_err(|_error| {
626 format_err_spanned!(
627 lit_str,
628 "encountered invalid Rust identifier for namespace argument",
629 )
630 })?;
631 Ok(Namespace::from(argument.into_bytes()))
632 } else {
633 Err(format_err_spanned!(
634 value,
635 "expected string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]",
636 ))
637 }
638 }
639}
640
641impl From<Vec<u8>> for Namespace {
642 fn from(bytes: Vec<u8>) -> Self {
643 Self { bytes }
644 }
645}
646
647impl Namespace {
648 pub fn as_bytes(&self) -> &[u8] {
650 &self.bytes
651 }
652}
653
654pub fn contains_ink_attributes<'a, I>(attrs: I) -> bool
662where
663 I: IntoIterator<Item = &'a syn::Attribute>,
664{
665 attrs.into_iter().any(|attr| attr.path().is_ident("ink"))
666}
667
668pub fn first_ink_attribute<'a, I>(
676 attrs: I,
677) -> Result<Option<ir::InkAttribute>, syn::Error>
678where
679 I: IntoIterator<Item = &'a syn::Attribute>,
680{
681 let first = attrs.into_iter().find(|attr| attr.path().is_ident("ink"));
682 match first {
683 None => Ok(None),
684 Some(ink_attr) => InkAttribute::try_from(ink_attr).map(Some),
685 }
686}
687
688pub fn partition_attributes<I>(
694 attrs: I,
695) -> Result<(Vec<InkAttribute>, Vec<syn::Attribute>), syn::Error>
696where
697 I: IntoIterator<Item = syn::Attribute>,
698{
699 use either::Either;
700 use itertools::Itertools as _;
701 let (ink_attrs, others) = attrs
702 .into_iter()
703 .map(<Attribute as TryFrom<_>>::try_from)
704 .collect::<Result<Vec<Attribute>, syn::Error>>()?
705 .into_iter()
706 .partition_map(|attr| {
707 match attr {
708 Attribute::Ink(ink_attr) => Either::Left(ink_attr),
709 Attribute::Other(other_attr) => Either::Right(other_attr),
710 }
711 });
712 Attribute::ensure_no_duplicate_attrs(&ink_attrs)?;
713 Ok((ink_attrs, others))
714}
715
716pub fn sanitize_attributes<I, C>(
739 parent_span: Span,
740 attrs: I,
741 is_valid_first: &ir::AttributeArgKind,
742 is_conflicting_attr: C,
743) -> Result<(InkAttribute, Vec<syn::Attribute>), syn::Error>
744where
745 I: IntoIterator<Item = syn::Attribute>,
746 C: FnMut(&ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
747{
748 let (ink_attrs, other_attrs) = ir::partition_attributes(attrs)?;
749 let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
750 err.into_combine(format_err!(parent_span, "at this invocation",))
751 })?;
752 normalized.ensure_first(is_valid_first).map_err(|err| {
753 err.into_combine(format_err!(
754 parent_span,
755 "expected {} as first ink! attribute argument",
756 is_valid_first,
757 ))
758 })?;
759 normalized.ensure_no_conflicts(is_conflicting_attr)?;
760 Ok((normalized, other_attrs))
761}
762
763pub fn sanitize_optional_attributes<I, C>(
784 parent_span: Span,
785 attrs: I,
786 is_conflicting_attr: C,
787) -> Result<(Option<InkAttribute>, Vec<syn::Attribute>), syn::Error>
788where
789 I: IntoIterator<Item = syn::Attribute>,
790 C: FnMut(&ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
791{
792 let (ink_attrs, rust_attrs) = ir::partition_attributes(attrs)?;
793 if ink_attrs.is_empty() {
794 return Ok((None, rust_attrs));
795 }
796 let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
797 err.into_combine(format_err!(parent_span, "at this invocation",))
798 })?;
799 normalized.ensure_no_conflicts(is_conflicting_attr)?;
800 Ok((Some(normalized), rust_attrs))
801}
802
803impl Attribute {
804 fn ensure_no_duplicate_attrs<'a, I>(attrs: I) -> Result<(), syn::Error>
811 where
812 I: IntoIterator<Item = &'a InkAttribute>,
813 {
814 use std::collections::HashSet;
815 let mut seen: HashSet<&InkAttribute> = HashSet::new();
816 for attr in attrs.into_iter() {
817 if let Some(seen) = seen.get(attr) {
818 use crate::error::ExtError as _;
819 return Err(format_err!(
820 attr.span(),
821 "encountered duplicate ink! attribute"
822 )
823 .into_combine(format_err!(seen.span(), "first ink! attribute here")));
824 }
825 seen.insert(attr);
826 }
827 Ok(())
828 }
829}
830
831impl TryFrom<syn::Attribute> for Attribute {
832 type Error = syn::Error;
833
834 fn try_from(attr: syn::Attribute) -> Result<Self, Self::Error> {
835 if attr.path().is_ident("ink") {
836 return <InkAttribute as TryFrom<_>>::try_from(&attr).map(Into::into);
837 }
838 Ok(Attribute::Other(attr))
839 }
840}
841
842impl From<InkAttribute> for Attribute {
843 fn from(ink_attribute: InkAttribute) -> Self {
844 Attribute::Ink(ink_attribute)
845 }
846}
847
848impl TryFrom<&syn::Attribute> for InkAttribute {
849 type Error = syn::Error;
850
851 fn try_from(attr: &syn::Attribute) -> Result<Self, Self::Error> {
852 if !attr.path().is_ident("ink") {
853 return Err(format_err_spanned!(attr, "unexpected non-ink! attribute"));
854 }
855
856 let args: Vec<_> = attr
857 .parse_args_with(Punctuated::<AttributeFrag, Token![,]>::parse_terminated)?
858 .into_iter()
859 .collect();
860
861 Self::ensure_no_duplicate_args(&args)?;
862 if args.is_empty() {
863 return Err(format_err_spanned!(
864 attr,
865 "encountered unsupported empty ink! attribute"
866 ));
867 }
868 Ok(InkAttribute { args })
869 }
870}
871
872impl InkAttribute {
873 pub fn ensure_no_conflicts<'a, P>(
885 &'a self,
886 mut is_conflicting: P,
887 ) -> Result<(), syn::Error>
888 where
889 P: FnMut(&'a ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
890 {
891 let mut err: Option<syn::Error> = None;
892 for arg in self.args() {
893 if let Err(reason) = is_conflicting(arg) {
894 let conflict_err = format_err!(
895 arg.span(),
896 "encountered conflicting ink! attribute argument",
897 );
898 match &mut err {
899 Some(err) => {
900 err.combine(conflict_err);
901 }
902 None => {
903 err = Some(conflict_err);
904 }
905 }
906 if let Some(reason) = reason {
907 err.as_mut()
908 .expect("must be `Some` at this point")
909 .combine(reason);
910 }
911 }
912 }
913 if let Some(err) = err {
914 return Err(err);
915 }
916 Ok(())
917 }
918}
919
920impl Parse for AttributeFrag {
921 fn parse(input: ParseStream) -> syn::Result<Self> {
922 let ast: ast::Meta = input.parse()?;
923
924 let arg = match &ast {
925 ast::Meta::NameValue(name_value) => {
926 let ident = name_value.name.get_ident().ok_or_else(|| {
927 format_err_spanned!(
928 name_value.name,
929 "expected identifier for ink! attribute argument",
930 )
931 })?;
932 match ident.to_string().as_str() {
933 "selector" => {
934 SelectorOrWildcard::try_from(&name_value.value)
935 .map(AttributeArg::Selector)
936 }
937 "namespace" => {
938 Namespace::try_from(&name_value.value)
939 .map(AttributeArg::Namespace)
940 }
941 "signature_topic" => {
942 if let Some(hex_str) = name_value.value.to_string() {
943 let topic = SignatureTopic::try_from(hex_str.as_str())
944 .map_err(|err| {
945 syn::Error::new_spanned(&name_value.value, err)
946 })?;
947 Ok(AttributeArg::SignatureTopic(topic))
948 } else {
949 Err(format_err_spanned!(
950 name_value.value,
951 "expected String type for `S` in #[ink(signature_topic = S)]",
952 ))
953 }
954 }
955 "name" => {
956 let name =
957 extract_name_override(&name_value.value, name_value.span())?;
958 Ok(AttributeArg::Name(name.value().to_string()))
959 }
960 _ => {
961 Err(format_err_spanned!(
962 ident,
963 "encountered unknown ink! attribute argument: {}",
964 ident
965 ))
966 }
967 }
968 }
969 ast::Meta::Path(path) => {
970 let ident = path.get_ident().ok_or_else(|| {
971 format_err_spanned!(
972 path,
973 "expected identifier for ink! attribute argument",
974 )
975 })?;
976 match ident.to_string().as_str() {
977 "storage" => Ok(AttributeArg::Storage),
978 "message" => Ok(AttributeArg::Message),
979 "constructor" => Ok(AttributeArg::Constructor),
980 "event" => Ok(AttributeArg::Event),
981 "anonymous" => Ok(AttributeArg::Anonymous),
982 "payable" => Ok(AttributeArg::Payable),
983 "default" => Ok(AttributeArg::Default),
984 "impl" => Ok(AttributeArg::Implementation),
985 _ => {
986 match ident.to_string().as_str() {
987 "function" => {
988 Err(format_err_spanned!(
989 path,
990 "encountered #[ink(function)] that is missing its `id` parameter. \
991 Did you mean #[ink(function = id: u16)] ?"
992 ))
993 }
994 "namespace" => {
995 Err(format_err_spanned!(
996 path,
997 "encountered #[ink(namespace)] that is missing its string parameter. \
998 Did you mean #[ink(namespace = name: str)] ?"
999 ))
1000 }
1001 "selector" => {
1002 Err(format_err_spanned!(
1003 path,
1004 "encountered #[ink(selector)] that is missing its u32 parameter. \
1005 Did you mean #[ink(selector = value: u32)] ?"
1006 ))
1007 }
1008 "name" => {
1009 Err(format_err_spanned!(
1010 path,
1011 "expected a string literal value for `name` \
1012 attribute argument"
1013 ))
1014 }
1015 _ => {
1016 Err(format_err_spanned!(
1017 path,
1018 "encountered unknown ink! attribute argument: {}",
1019 ident
1020 ))
1021 }
1022 }
1023 }
1024 }
1025 }
1026 }?;
1027
1028 Ok(Self { ast, arg })
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035
1036 #[test]
1037 fn contains_ink_attributes_works() {
1038 assert!(!contains_ink_attributes(&[]));
1039 assert!(contains_ink_attributes(&[syn::parse_quote! { #[ink] }]));
1040 assert!(contains_ink_attributes(&[syn::parse_quote! { #[ink(..)] }]));
1041 assert!(contains_ink_attributes(&[
1042 syn::parse_quote! { #[inline] },
1043 syn::parse_quote! { #[likely] },
1044 syn::parse_quote! { #[ink(storage)] },
1045 ]));
1046 assert!(!contains_ink_attributes(&[
1047 syn::parse_quote! { #[inline] },
1048 syn::parse_quote! { #[likely] },
1049 ]));
1050 }
1051
1052 fn assert_first_ink_attribute(
1059 input: &[syn::Attribute],
1060 expected: Result<Option<Vec<ir::AttributeArg>>, &'static str>,
1061 ) {
1062 assert_eq!(
1063 first_ink_attribute(input)
1064 .map(|maybe_attr: Option<ir::InkAttribute>| {
1065 maybe_attr.map(|attr: ir::InkAttribute| {
1066 attr.args.into_iter().map(|arg| arg.arg).collect::<Vec<_>>()
1067 })
1068 })
1069 .map_err(|err| err.to_string()),
1070 expected.map_err(ToString::to_string),
1071 )
1072 }
1073
1074 #[test]
1075 fn first_ink_attribute_works() {
1076 assert_first_ink_attribute(&[], Ok(None));
1077 assert_first_ink_attribute(
1078 &[syn::parse_quote! { #[ink(storage)] }],
1079 Ok(Some(vec![AttributeArg::Storage])),
1080 );
1081 assert_first_ink_attribute(
1082 &[syn::parse_quote! { #[ink(invalid)] }],
1083 Err("encountered unknown ink! attribute argument: invalid"),
1084 );
1085 }
1086
1087 mod test {
1088 use crate::ir;
1089
1090 #[derive(Debug, PartialEq, Eq)]
1092 #[allow(clippy::large_enum_variant)] pub enum Attribute {
1094 Ink(Vec<ir::AttributeArg>),
1095 Other(syn::Attribute),
1096 }
1097
1098 impl From<ir::Attribute> for Attribute {
1099 fn from(attr: ir::Attribute) -> Self {
1100 match attr {
1101 ir::Attribute::Ink(ink_attr) => {
1102 Self::Ink(
1103 ink_attr
1104 .args
1105 .into_iter()
1106 .map(|arg| arg.arg)
1107 .collect::<Vec<_>>(),
1108 )
1109 }
1110 ir::Attribute::Other(other_attr) => Self::Other(other_attr),
1111 }
1112 }
1113 }
1114
1115 impl From<ir::InkAttribute> for Attribute {
1116 fn from(ink_attr: ir::InkAttribute) -> Self {
1117 Attribute::from(ir::Attribute::Ink(ink_attr))
1118 }
1119 }
1120
1121 #[derive(Debug, PartialEq, Eq)]
1123 pub struct InkAttribute {
1124 args: Vec<ir::AttributeArg>,
1125 }
1126
1127 impl From<ir::InkAttribute> for InkAttribute {
1128 fn from(ink_attr: ir::InkAttribute) -> Self {
1129 Self {
1130 args: ink_attr
1131 .args
1132 .into_iter()
1133 .map(|arg| arg.arg)
1134 .collect::<Vec<_>>(),
1135 }
1136 }
1137 }
1138
1139 impl<I> From<I> for InkAttribute
1140 where
1141 I: IntoIterator<Item = ir::AttributeArg>,
1142 {
1143 fn from(args: I) -> Self {
1144 Self {
1145 args: args.into_iter().collect::<Vec<_>>(),
1146 }
1147 }
1148 }
1149 }
1150
1151 fn assert_attribute_try_from(
1154 input: syn::Attribute,
1155 expected: Result<test::Attribute, &'static str>,
1156 ) {
1157 assert_eq!(
1158 <ir::Attribute as TryFrom<_>>::try_from(input)
1159 .map(test::Attribute::from)
1160 .map_err(|err| err.to_string()),
1161 expected.map_err(ToString::to_string),
1162 )
1163 }
1164
1165 #[test]
1166 fn storage_works() {
1167 assert_attribute_try_from(
1168 syn::parse_quote! {
1169 #[ink(storage)]
1170 },
1171 Ok(test::Attribute::Ink(vec![AttributeArg::Storage])),
1172 );
1173 }
1174
1175 #[test]
1178 fn impl_works() {
1179 assert_attribute_try_from(
1180 syn::parse_quote! {
1181 #[ink(impl)]
1182 },
1183 Ok(test::Attribute::Ink(vec![AttributeArg::Implementation])),
1184 );
1185 }
1186
1187 #[test]
1188 fn selector_works() {
1189 assert_attribute_try_from(
1190 syn::parse_quote! {
1191 #[ink(selector = 42)]
1192 },
1193 Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
1194 SelectorOrWildcard::UserProvided(Selector::from([0, 0, 0, 42])),
1195 )])),
1196 );
1197 assert_attribute_try_from(
1198 syn::parse_quote! {
1199 #[ink(selector = 0xDEADBEEF)]
1200 },
1201 Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
1202 SelectorOrWildcard::selector([0xDE, 0xAD, 0xBE, 0xEF]),
1203 )])),
1204 );
1205 }
1206
1207 #[test]
1208 fn wildcard_selector_works() {
1209 assert_attribute_try_from(
1210 syn::parse_quote! {
1211 #[ink(selector = _)]
1212 },
1213 Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
1214 SelectorOrWildcard::Wildcard,
1215 )])),
1216 );
1217 }
1218
1219 #[test]
1220 fn selector_negative_number() {
1221 assert_attribute_try_from(
1222 syn::parse_quote! {
1223 #[ink(selector = -1)]
1224 },
1225 Err(
1226 "selector value out of range. selector must be a valid `u32` integer: \
1227 invalid digit found in string",
1228 ),
1229 );
1230 }
1231
1232 #[test]
1233 fn selector_out_of_range() {
1234 assert_attribute_try_from(
1235 syn::parse_quote! {
1236 #[ink(selector = 0xFFFF_FFFF_FFFF_FFFF)]
1237 },
1238 Err("selector value out of range. \
1239 selector must be a valid `u32` integer: number too large to fit in target type"),
1240 );
1241 }
1242
1243 #[test]
1244 fn selector_invalid_type() {
1245 assert_attribute_try_from(
1246 syn::parse_quote! {
1247 #[ink(selector = true)]
1248 },
1249 Err(
1250 "expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]",
1251 ),
1252 );
1253 }
1254
1255 #[test]
1256 fn default_works() {
1257 assert_attribute_try_from(
1258 syn::parse_quote! {
1259 #[ink(default)]
1260 },
1261 Ok(test::Attribute::Ink(vec![AttributeArg::Default])),
1262 )
1263 }
1264
1265 #[test]
1266 fn namespace_works() {
1267 assert_attribute_try_from(
1268 syn::parse_quote! {
1269 #[ink(namespace = "my_namespace")]
1270 },
1271 Ok(test::Attribute::Ink(vec![AttributeArg::Namespace(
1272 Namespace::from("my_namespace".to_string().into_bytes()),
1273 )])),
1274 );
1275 }
1276
1277 #[test]
1278 fn namespace_invalid_identifier() {
1279 assert_attribute_try_from(
1280 syn::parse_quote! {
1281 #[ink(namespace = "::invalid_identifier")]
1282 },
1283 Err("encountered invalid Rust identifier for namespace argument"),
1284 );
1285 }
1286
1287 #[test]
1288 fn namespace_invalid_type() {
1289 assert_attribute_try_from(
1290 syn::parse_quote! {
1291 #[ink(namespace = 42)]
1292 },
1293 Err(
1294 "expected string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]",
1295 ),
1296 );
1297 }
1298
1299 #[test]
1300 fn namespace_missing_parameter() {
1301 assert_attribute_try_from(
1302 syn::parse_quote! {
1303 #[ink(namespace)]
1304 },
1305 Err(
1306 "encountered #[ink(namespace)] that is missing its string parameter. \
1307 Did you mean #[ink(namespace = name: str)] ?",
1308 ),
1309 );
1310 }
1311
1312 #[test]
1313 fn compound_mixed_works() {
1314 assert_attribute_try_from(
1315 syn::parse_quote! {
1316 #[ink(message, namespace = "my_namespace")]
1317 },
1318 Ok(test::Attribute::Ink(vec![
1319 AttributeArg::Message,
1320 AttributeArg::Namespace(Namespace::from(
1321 "my_namespace".to_string().into_bytes(),
1322 )),
1323 ])),
1324 )
1325 }
1326
1327 #[test]
1328 fn compound_simple_works() {
1329 assert_attribute_try_from(
1330 syn::parse_quote! {
1331 #[ink(
1332 storage,
1333 message,
1334 constructor,
1335 event,
1336 payable,
1337 impl,
1338 )]
1339 },
1340 Ok(test::Attribute::Ink(vec![
1341 AttributeArg::Storage,
1342 AttributeArg::Message,
1343 AttributeArg::Constructor,
1344 AttributeArg::Event,
1345 AttributeArg::Payable,
1346 AttributeArg::Implementation,
1347 ])),
1348 );
1349 }
1350
1351 #[test]
1352 fn non_ink_attribute_works() {
1353 let attr: syn::Attribute = syn::parse_quote! {
1354 #[non_ink(message)]
1355 };
1356 assert_attribute_try_from(attr.clone(), Ok(test::Attribute::Other(attr)));
1357 }
1358
1359 #[test]
1360 fn empty_ink_attribute_fails() {
1361 assert_attribute_try_from(
1362 syn::parse_quote! {
1363 #[ink]
1364 },
1365 Err("expected attribute arguments in parentheses: #[ink(...)]"),
1366 );
1367 assert_attribute_try_from(
1368 syn::parse_quote! {
1369 #[ink()]
1370 },
1371 Err("encountered unsupported empty ink! attribute"),
1372 );
1373 }
1374
1375 #[test]
1376 fn duplicate_flags_fails() {
1377 assert_attribute_try_from(
1378 syn::parse_quote! {
1379 #[ink(message, message)]
1380 },
1381 Err("encountered duplicate ink! attribute arguments"),
1382 );
1383 }
1384
1385 fn assert_parition_attributes(
1389 input: Vec<syn::Attribute>,
1390 expected: Result<(Vec<test::InkAttribute>, Vec<syn::Attribute>), &'static str>,
1391 ) {
1392 assert_eq!(
1393 partition_attributes(input)
1394 .map(|(ink_attr, other_attr)| {
1395 (
1396 ink_attr
1397 .into_iter()
1398 .map(test::InkAttribute::from)
1399 .collect::<Vec<_>>(),
1400 other_attr,
1401 )
1402 })
1403 .map_err(|err| err.to_string()),
1404 expected.map_err(ToString::to_string)
1405 );
1406 }
1407
1408 #[test]
1409 fn parition_attributes_works() {
1410 assert_parition_attributes(
1411 vec![
1412 syn::parse_quote! { #[ink(message)] },
1413 syn::parse_quote! { #[non_ink_attribute] },
1414 ],
1415 Ok((
1416 vec![test::InkAttribute::from(vec![AttributeArg::Message])],
1417 vec![syn::parse_quote! { #[non_ink_attribute] }],
1418 )),
1419 )
1420 }
1421
1422 #[test]
1423 fn parition_duplicates_fails() {
1424 assert_parition_attributes(
1425 vec![
1426 syn::parse_quote! { #[ink(message)] },
1427 syn::parse_quote! { #[ink(message)] },
1428 ],
1429 Err("encountered duplicate ink! attribute"),
1430 )
1431 }
1432
1433 #[test]
1434 fn signature_topic_works() {
1435 let s = "11".repeat(32);
1436 let bytes = [0x11u8; 32];
1437 assert_attribute_try_from(
1438 syn::parse_quote! {
1439 #[ink(signature_topic = #s)]
1440 },
1441 Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic(
1442 SignatureTopic::from(bytes),
1443 )])),
1444 );
1445 }
1446
1447 #[test]
1448 fn signature_topic_invalid_length_fails() {
1449 let s = "11".repeat(16);
1450 assert_attribute_try_from(
1451 syn::parse_quote! {
1452 #[ink(signature_topic = #s)]
1453 },
1454 Err("`signature_topic` is expected to be 32-byte hex string. \
1455 Found 16 bytes"),
1456 );
1457 }
1458
1459 #[test]
1460 fn signature_topic_invalid_hex_fails() {
1461 let s = "XY".repeat(32);
1462 assert_attribute_try_from(
1463 syn::parse_quote! {
1464 #[ink(signature_topic = #s)]
1465 },
1466 Err("`signature_topic` has invalid hex string"),
1467 );
1468 }
1469
1470 #[test]
1471 fn name_works() {
1472 assert_attribute_try_from(
1473 syn::parse_quote! {
1474 #[ink(name = "my_name1")]
1475 },
1476 Ok(test::Attribute::Ink(vec![AttributeArg::Name(
1477 "my_name1".to_string(),
1478 )])),
1479 );
1480 assert_attribute_try_from(
1482 syn::parse_quote! {
1483 #[ink(name = "$myName1")]
1484 },
1485 Ok(test::Attribute::Ink(vec![AttributeArg::Name(
1486 "$myName1".to_string(),
1487 )])),
1488 );
1489 }
1490
1491 #[test]
1492 fn name_invalid_identifier() {
1493 assert_attribute_try_from(
1494 syn::parse_quote! {
1495 #[ink(name = "::invalid_identifier")]
1496 },
1497 Err("`name` attribute argument value must begin with an \
1498 alphabetic character, underscore or dollar sign"),
1499 );
1500 assert_attribute_try_from(
1501 syn::parse_quote! {
1502 #[ink(name = "1MyName")]
1503 },
1504 Err("`name` attribute argument value must begin with an \
1505 alphabetic character, underscore or dollar sign"),
1506 );
1507 assert_attribute_try_from(
1508 syn::parse_quote! {
1509 #[ink(name = "invalid::identifier")]
1510 },
1511 Err("`name` attribute argument value can only contain \
1512 alphanumeric characters, underscores and dollar signs"),
1513 );
1514 assert_attribute_try_from(
1515 syn::parse_quote! {
1516 #[ink(name = "My-Name")]
1517 },
1518 Err("`name` attribute argument value can only contain \
1519 alphanumeric characters, underscores and dollar signs"),
1520 );
1521 }
1522
1523 #[test]
1524 fn name_invalid_type() {
1525 assert_attribute_try_from(
1526 syn::parse_quote! {
1527 #[ink(name = 42)]
1528 },
1529 Err("expected a string literal value for `name` attribute argument"),
1530 );
1531 }
1532
1533 #[test]
1534 fn name_missing_value() {
1535 assert_attribute_try_from(
1536 syn::parse_quote! {
1537 #[ink(name)]
1538 },
1539 Err("expected a string literal value for `name` attribute argument"),
1540 );
1541 }
1542}