ink_ir/ir/
attrs.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
45/// An extension trait for [`syn::Attribute`] in order to query for documentation.
46pub trait IsDocAttribute {
47    /// Returns `true` if the attribute is a Rust documentation attribute.
48    fn is_doc_attribute(&self) -> bool;
49
50    /// Returns the contents of the Rust documentation attribute or `None`.
51    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)] // todo
78/// Either an ink! specific attribute, or another uninterpreted attribute.
79#[derive(Debug, PartialEq, Eq)]
80pub enum Attribute {
81    /// An ink! specific attribute, e.g. `#[ink(storage)]`.
82    Ink(InkAttribute),
83    /// Any other attribute.
84    ///
85    /// This can be a known `#[derive(Debug)]` or a specific attribute of another
86    /// crate.
87    Other(syn::Attribute),
88}
89
90/// Types implementing this trait can return a slice over their `syn` attributes.
91pub trait Attrs {
92    /// Returns the slice of attributes of an AST entity.
93    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/// An ink! specific attribute.
133///
134/// # Examples
135///
136/// An attribute with a simple flag:
137/// ```no_compile
138/// #[ink(storage)]
139/// ```
140///
141/// An attribute with a parameterized flag:
142/// ```no_compile
143/// #[ink(selector = 0xDEADBEEF)]
144/// ```
145///
146/// An attribute with multiple flags:
147/// ```no_compile
148/// #[ink(message, payable, selector = 0xDEADBEEF)]
149/// ```
150#[derive(Debug, Clone, PartialEq, Eq, Hash)]
151pub struct InkAttribute {
152    /// The internal non-empty sequence of arguments of the ink! attribute.
153    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    /// Ensure that the first ink! attribute argument is of expected kind.
166    ///
167    /// # Errors
168    ///
169    /// If the first ink! attribute argument is not of expected kind.
170    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    /// Ensures that the given iterator of ink! attribute arguments do not have
181    /// duplicates.
182    ///
183    /// # Errors
184    ///
185    /// If the given iterator yields duplicate ink! attribute arguments.
186    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    /// Converts a sequence of `#[ink(...)]` attributes into a single flattened
222    /// `#[ink(...)]` attribute that contains all of the input arguments.
223    ///
224    /// # Example
225    ///
226    /// Given the input ink! attribute sequence `[ #[ink(message)], #[ink(payable)] ]`
227    /// this procedure returns the single attribute `#[ink(message, payable)]`.
228    ///
229    /// # Errors
230    ///
231    /// - If the sequence of input ink! attributes contains duplicates.
232    /// - If the input sequence is empty.
233    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    /// Returns the first ink! attribute argument.
252    pub fn first(&self) -> &AttributeFrag {
253        self.args
254            .first()
255            .expect("encountered invalid empty ink! attribute list")
256    }
257
258    /// Returns an iterator over the non-empty flags of the ink! attribute.
259    ///
260    /// # Note
261    ///
262    /// This yields at least one ink! attribute flag.
263    pub fn args(&self) -> ::core::slice::Iter<'_, AttributeFrag> {
264        self.args.iter()
265    }
266
267    /// Returns the namespace of the ink! attribute if any.
268    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    /// Returns the selector of the ink! attribute if any.
278    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    /// Returns the signature topic of the ink! attribute if any.
288    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    /// Returns `true` if the ink! attribute contains the `payable` argument.
298    pub fn is_payable(&self) -> bool {
299        self.args()
300            .any(|arg| matches!(arg.kind(), AttributeArg::Payable))
301    }
302
303    /// Returns `true` if the ink! attribute contains the `default` argument.
304    pub fn is_default(&self) -> bool {
305        self.args()
306            .any(|arg| matches!(arg.kind(), AttributeArg::Default))
307    }
308
309    /// Returns `true` if the ink! attribute contains the wildcard selector.
310    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    /// Returns `true` if the ink! attribute contains the `anonymous` argument.
320    pub fn is_anonymous(&self) -> bool {
321        self.args()
322            .any(|arg| matches!(arg.kind(), AttributeArg::Anonymous))
323    }
324
325    /// Returns the name override value if any.
326    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/// An ink! specific attribute argument.
337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub struct AttributeFrag {
339    ast: ast::Meta,
340    arg: AttributeArg,
341}
342
343impl AttributeFrag {
344    /// Returns a shared reference to the attribute argument kind.
345    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/// The kind of an ink! attribute argument.
357#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
358pub enum AttributeArgKind {
359    /// `#[ink(storage)]`
360    Storage,
361    /// `#[ink(event)]`
362    Event,
363    /// `#[ink(anonymous)]`
364    Anonymous,
365    /// `#[ink(message)]`
366    Message,
367    /// `#[ink(constructor)]`
368    Constructor,
369    /// `#[ink(payable)]`
370    Payable,
371    /// `#[ink(default)]`
372    Default,
373    /// `#[ink(selector = _)]`
374    /// `#[ink(selector = 0xDEADBEEF)]`
375    Selector,
376    /// `#[ink(signature_topic =
377    /// "325c98ff66bd0d9d1c10789ae1f2a17bdfb2dcf6aa3d8092669afafdef1cb72d")]`
378    SignatureTopicArg,
379    /// `#[ink(namespace = "my_namespace")]`
380    Namespace,
381    /// `#[ink(impl)]`
382    Implementation,
383    /// `#[ink(name = "myName")]`
384    Name,
385}
386
387/// An ink! specific attribute flag.
388#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
389pub enum AttributeArg {
390    /// `#[ink(storage)]`
391    ///
392    /// Applied on `struct` types in order to flag them for being the
393    /// contract's storage definition.
394    Storage,
395    /// `#[ink(event)]`
396    ///
397    /// Applied on `struct` types in order to flag them for being an ink! event.
398    Event,
399    /// `#[ink(anonymous)]`
400    ///
401    /// Applied on `struct` event types in order to flag them as anonymous.
402    /// Anonymous events have similar semantics as in Solidity in that their
403    /// event signature won't be included in their event topics serialization
404    /// to reduce event emitting overhead. This is especially useful for user
405    /// defined events.
406    Anonymous,
407    /// `#[ink(message)]`
408    ///
409    /// Applied on `&self` or `&mut self` methods to flag them for being an ink!
410    /// exported message.
411    Message,
412    /// `#[ink(constructor)]`
413    ///
414    /// Applied on inherent methods returning `Self` to flag them for being ink!
415    /// exported contract constructors.
416    Constructor,
417    /// `#[ink(payable)]`
418    ///
419    /// Applied on ink! constructors or messages in order to specify that they
420    /// can receive funds from callers.
421    Payable,
422    /// Applied on ink! constructors or messages in order to indicate
423    /// they are default.
424    Default,
425    /// Can be either one of:
426    ///
427    /// - `#[ink(selector = 0xDEADBEEF)]` Applied on ink! constructors or messages to
428    ///   manually control their selectors.
429    /// - `#[ink(selector = _)]` Applied on ink! messages to define a fallback messages
430    ///   that is invoked if no other ink! message matches a given selector.
431    Selector(SelectorOrWildcard),
432    /// `#[ink(signature_topic =
433    /// "325c98ff66bd0d9d1c10789ae1f2a17bdfb2dcf6aa3d8092669afafdef1cb72d")]`
434    SignatureTopic(SignatureTopic),
435    /// `#[ink(namespace = "my_namespace")]`
436    ///
437    /// Applied on ink! trait implementation blocks to disambiguate other trait
438    /// implementation blocks with equal names.
439    Namespace(Namespace),
440    /// `#[ink(impl)]`
441    ///
442    /// This attribute supports a niche case that is rarely needed.
443    ///
444    /// Can be applied on ink! implementation blocks in order to make ink! aware
445    /// of them. This is useful if such an implementation block does not contain
446    /// any other ink! attributes, so it would be flagged by ink! as a Rust item.
447    /// Adding `#[ink(impl)]` on such implementation blocks makes them treated
448    /// as ink! implementation blocks thus allowing to access the environment, etc..
449    /// Note that ink! messages and constructors still need to be explicitly
450    /// flagged as such.
451    Implementation,
452    /// `#[ink(name = "myName")]`
453    ///
454    /// Applied on ink! messages, constructors and events to provide the name override
455    /// for the item.
456    ///
457    /// # Note
458    ///
459    /// - Useful for defining overloaded interfaces.
460    /// - If provided, the name must be a valid "identifier-like" string.
461    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    /// Returns the kind of the ink! attribute argument.
491    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/// Either a wildcard selector or a specified selector.
533#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
534pub enum SelectorOrWildcard {
535    /// A wildcard selector. If no other selector matches, the message/constructor
536    /// annotated with the wildcard selector will be invoked.
537    Wildcard,
538    /// A user provided selector.
539    UserProvided(Selector),
540}
541
542impl SelectorOrWildcard {
543    /// Create a new `SelectorOrWildcard::Selector` from the supplied bytes.
544    fn selector(bytes: [u8; 4]) -> Self {
545        SelectorOrWildcard::UserProvided(Selector::from(bytes))
546    }
547
548    /// The selector of the wildcard complement message.
549    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/// An ink! namespace applicable to a trait implementation block.
613#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
614pub struct Namespace {
615    /// The underlying bytes.
616    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    /// Returns the namespace as bytes.
649    pub fn as_bytes(&self) -> &[u8] {
650        &self.bytes
651    }
652}
653
654/// Returns `true` if the given iterator yields at least one attribute of the form
655/// `#[ink(...)]` or `#[ink]`.
656///
657/// # Note
658///
659/// This does not check at this point whether the ink! attribute is valid since
660/// this check is optimized for efficiency.
661pub 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
668/// Returns the first valid ink! attribute, if any.
669///
670/// Returns `None` if there are no ink! attributes.
671///
672/// # Errors
673///
674/// Returns an error if the first ink! attribute is invalid.
675pub 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
688/// Partitions the given attributes into ink! specific and non-ink! specific attributes.
689///
690/// # Error
691///
692/// Returns an error if some ink! specific attributes could not be successfully parsed.
693pub 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
716/// Sanitizes the given attributes.
717///
718/// This partitions the attributes into ink! and non-ink! attributes.
719/// All ink! attributes are normalized, they are checked to have a valid first
720/// ink! attribute argument and no conflicts given the conflict predicate.
721///
722/// Returns the partitioned ink! and non-ink! attributes.
723///
724/// # Parameters
725///
726/// The `is_conflicting_attr` closure returns `Ok` if the attribute does not conflict,
727/// returns `Err(None)` if the attribute conflicts but without providing further reasoning
728/// and `Err(Some(reason))` if the attribute conflicts given additional context
729/// information.
730///
731/// # Errors
732///
733/// - If there are invalid ink! attributes.
734/// - If there are duplicate ink! attributes.
735/// - If the first ink! attribute is not matching the expected.
736/// - If there are conflicting ink! attributes.
737/// - if there are no ink! attributes.
738pub 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
763/// Sanitizes the given optional attributes.
764///
765/// This partitions the attributes into ink! and non-ink! attributes.
766/// If there are ink! attributes they are normalized and deduplicated.
767/// Also checks to guard against conflicting ink! attributes are provided.
768///
769/// Returns the optional partitioned ink! and non-ink! attributes.
770///
771/// # Parameters
772///
773/// The `is_conflicting_attr` closure returns `Ok` if the attribute does not conflict,
774/// returns `Err(None)` if the attribute conflicts but without providing further reasoning
775/// and `Err(Some(reason))` if the attribute conflicts given additional context
776/// information.
777///
778/// # Errors
779///
780/// - If there are invalid ink! attributes.
781/// - If there are duplicate ink! attributes.
782/// - If there are conflicting ink! attributes.
783pub 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    /// Returns `Ok` if the given iterator yields no duplicate ink! attributes.
805    ///
806    /// # Errors
807    ///
808    /// If the given iterator yields duplicate ink! attributes.
809    /// Note: Duplicate non-ink! attributes are fine.
810    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    /// Ensures that there are no conflicting ink! attribute arguments in `self`.
874    ///
875    /// The given `is_conflicting` describes for every ink! attribute argument
876    /// found in `self` if it is in conflict.
877    ///
878    /// # Parameters
879    ///
880    /// The `is_conflicting_attr` closure returns `Ok` if the attribute does not conflict,
881    /// returns `Err(None)` if the attribute conflicts but without providing further
882    /// reasoning and `Err(Some(reason))` if the attribute conflicts given additional
883    /// context information.
884    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    /// Asserts that the given input yields the expected first argument or the
1053    /// expected error string.
1054    ///
1055    /// # Note
1056    ///
1057    /// Can be used to assert against the success and failure path.
1058    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        /// Mock for `ir::Attribute` to improve the ability to test.
1091        #[derive(Debug, PartialEq, Eq)]
1092        #[allow(clippy::large_enum_variant)] // todo
1093        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        /// Mock for `ir::InkAttribute` to improve the ability to test.
1122        #[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    /// Asserts that the given [`syn::Attribute`] is converted into the expected
1152    /// [`ir::Attribute`] or yields the expected error message.
1153    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    /// This tests that `#[ink(impl)]` works which can be non-trivial since
1176    /// `impl` is also a Rust keyword.
1177    #[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    /// Asserts that the given sequence of [`syn::Attribute`] is correctly
1386    /// partitioned into the expected tuple of ink! and non-ink! attributes
1387    /// or that the expected error is returned.
1388    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        // Solidity-like identifier works
1481        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}