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    parse::{
26        Parse,
27        ParseStream,
28    },
29    punctuated::Punctuated,
30    spanned::Spanned,
31    Token,
32};
33
34use crate::{
35    ast,
36    error::ExtError as _,
37    ir,
38    ir::{
39        chain_extension::FunctionId,
40        Selector,
41    },
42};
43
44/// An extension trait for [`syn::Attribute`] in order to query for documentation.
45pub trait IsDocAttribute {
46    /// Returns `true` if the attribute is a Rust documentation attribute.
47    fn is_doc_attribute(&self) -> bool;
48
49    /// Returns the contents of the Rust documentation attribute or `None`.
50    fn extract_docs(&self) -> Option<String>;
51}
52
53impl IsDocAttribute for syn::Attribute {
54    fn is_doc_attribute(&self) -> bool {
55        self.path().is_ident("doc")
56    }
57
58    fn extract_docs(&self) -> Option<String> {
59        if !self.is_doc_attribute() {
60            return None;
61        }
62        match &self.meta {
63            syn::Meta::NameValue(nv) => {
64                if let syn::Expr::Lit(l) = &nv.value {
65                    if let syn::Lit::Str(s) = &l.lit {
66                        return Some(s.value());
67                    }
68                }
69            }
70            _ => return None,
71        }
72        None
73    }
74}
75
76#[allow(clippy::large_enum_variant)] // todo
77/// Either an ink! specific attribute, or another uninterpreted attribute.
78#[derive(Debug, PartialEq, Eq)]
79pub enum Attribute {
80    /// An ink! specific attribute, e.g. `#[ink(storage)]`.
81    Ink(InkAttribute),
82    /// Any other attribute.
83    ///
84    /// This can be a known `#[derive(Debug)]` or a specific attribute of another
85    /// crate.
86    Other(syn::Attribute),
87}
88
89/// Types implementing this trait can return a slice over their `syn` attributes.
90pub trait Attrs {
91    /// Returns the slice of attributes of an AST entity.
92    fn attrs(&self) -> &[syn::Attribute];
93}
94
95impl Attrs for syn::ImplItem {
96    fn attrs(&self) -> &[syn::Attribute] {
97        match self {
98            syn::ImplItem::Const(item) => &item.attrs,
99            syn::ImplItem::Fn(item) => &item.attrs,
100            syn::ImplItem::Type(item) => &item.attrs,
101            syn::ImplItem::Macro(item) => &item.attrs,
102            _ => &[],
103        }
104    }
105}
106
107impl Attrs for syn::Item {
108    fn attrs(&self) -> &[syn::Attribute] {
109        use syn::Item;
110        match self {
111            Item::Const(syn::ItemConst { attrs, .. })
112            | Item::Enum(syn::ItemEnum { attrs, .. })
113            | Item::ExternCrate(syn::ItemExternCrate { attrs, .. })
114            | Item::Fn(syn::ItemFn { attrs, .. })
115            | Item::ForeignMod(syn::ItemForeignMod { attrs, .. })
116            | Item::Impl(syn::ItemImpl { attrs, .. })
117            | Item::Macro(syn::ItemMacro { attrs, .. })
118            | Item::Mod(syn::ItemMod { attrs, .. })
119            | Item::Static(syn::ItemStatic { attrs, .. })
120            | Item::Struct(syn::ItemStruct { attrs, .. })
121            | Item::Trait(syn::ItemTrait { attrs, .. })
122            | Item::TraitAlias(syn::ItemTraitAlias { attrs, .. })
123            | Item::Type(syn::ItemType { attrs, .. })
124            | Item::Union(syn::ItemUnion { attrs, .. })
125            | Item::Use(syn::ItemUse { attrs, .. }) => attrs,
126            _ => &[],
127        }
128    }
129}
130
131/// An ink! specific attribute.
132///
133/// # Examples
134///
135/// An attribute with a simple flag:
136/// ```no_compile
137/// #[ink(storage)]
138/// ```
139///
140/// An attribute with a parameterized flag:
141/// ```no_compile
142/// #[ink(selector = 0xDEADBEEF)]
143/// ```
144///
145/// An attribute with multiple flags:
146/// ```no_compile
147/// #[ink(message, payable, selector = 0xDEADBEEF)]
148/// ```
149#[derive(Debug, Clone, PartialEq, Eq, Hash)]
150pub struct InkAttribute {
151    /// The internal non-empty sequence of arguments of the ink! attribute.
152    args: Vec<AttributeFrag>,
153}
154
155impl ToTokens for InkAttribute {
156    fn to_tokens(&self, tokens: &mut TokenStream2) {
157        for arg in &self.args {
158            arg.to_tokens(tokens)
159        }
160    }
161}
162
163impl InkAttribute {
164    /// Ensure that the first ink! attribute argument is of expected kind.
165    ///
166    /// # Errors
167    ///
168    /// If the first ink! attribute argument is not of expected kind.
169    pub fn ensure_first(&self, expected: &AttributeArgKind) -> Result<(), syn::Error> {
170        if &self.first().arg.kind() != expected {
171            return Err(format_err!(
172                self.span(),
173                "unexpected first ink! attribute argument",
174            ));
175        }
176        Ok(())
177    }
178
179    /// Ensures that the given iterator of ink! attribute arguments do not have
180    /// duplicates.
181    ///
182    /// # Errors
183    ///
184    /// If the given iterator yields duplicate ink! attribute arguments.
185    fn ensure_no_duplicate_args<'a, A>(args: A) -> Result<(), syn::Error>
186    where
187        A: IntoIterator<Item = &'a ir::AttributeFrag>,
188    {
189        use crate::error::ExtError as _;
190        use std::collections::HashSet;
191        let mut seen: HashSet<&AttributeFrag> = HashSet::new();
192        let mut seen2: HashMap<AttributeArgKind, Span> = HashMap::new();
193        for arg in args.into_iter() {
194            if let Some(seen) = seen.get(arg) {
195                return Err(format_err!(
196                    arg.span(),
197                    "encountered duplicate ink! attribute arguments"
198                )
199                .into_combine(format_err!(
200                    seen.span(),
201                    "first equal ink! attribute argument here"
202                )));
203            }
204            if let Some(seen) = seen2.get(&arg.kind().kind()) {
205                return Err(format_err!(
206                    arg.span(),
207                    "encountered ink! attribute arguments with equal kinds"
208                )
209                .into_combine(format_err!(
210                    *seen,
211                    "first equal ink! attribute argument with equal kind here"
212                )));
213            }
214            seen.insert(arg);
215            seen2.insert(arg.kind().kind(), arg.span());
216        }
217        Ok(())
218    }
219
220    /// Converts a sequence of `#[ink(...)]` attributes into a single flattened
221    /// `#[ink(...)]` attribute that contains all of the input arguments.
222    ///
223    /// # Example
224    ///
225    /// Given the input ink! attribute sequence `[ #[ink(message)], #[ink(payable)] ]`
226    /// this procedure returns the single attribute `#[ink(message, payable)]`.
227    ///
228    /// # Errors
229    ///
230    /// - If the sequence of input ink! attributes contains duplicates.
231    /// - If the input sequence is empty.
232    pub fn from_expanded<A>(attrs: A) -> Result<Self, syn::Error>
233    where
234        A: IntoIterator<Item = Self>,
235    {
236        let args = attrs
237            .into_iter()
238            .flat_map(|attr| attr.args)
239            .collect::<Vec<_>>();
240        if args.is_empty() {
241            return Err(format_err!(
242                Span::call_site(),
243                "encountered unexpected empty expanded ink! attribute arguments",
244            ));
245        }
246        Self::ensure_no_duplicate_args(&args)?;
247        Ok(Self { args })
248    }
249
250    /// Returns the first ink! attribute argument.
251    pub fn first(&self) -> &AttributeFrag {
252        self.args
253            .first()
254            .expect("encountered invalid empty ink! attribute list")
255    }
256
257    /// Returns an iterator over the non-empty flags of the ink! attribute.
258    ///
259    /// # Note
260    ///
261    /// This yields at least one ink! attribute flag.
262    pub fn args(&self) -> ::core::slice::Iter<AttributeFrag> {
263        self.args.iter()
264    }
265
266    /// Returns the namespace of the ink! attribute if any.
267    pub fn namespace(&self) -> Option<ir::Namespace> {
268        self.args().find_map(|arg| {
269            if let ir::AttributeArg::Namespace(namespace) = arg.kind() {
270                return Some(namespace.clone());
271            }
272            None
273        })
274    }
275
276    /// Returns the selector of the ink! attribute if any.
277    pub fn selector(&self) -> Option<SelectorOrWildcard> {
278        self.args().find_map(|arg| {
279            if let ir::AttributeArg::Selector(selector) = arg.kind() {
280                return Some(*selector);
281            }
282            None
283        })
284    }
285
286    /// Returns the signature topic of the ink! attribute if any.
287    pub fn signature_topic_hex(&self) -> Option<String> {
288        self.args().find_map(|arg| {
289            if let ir::AttributeArg::SignatureTopic(hash) = arg.kind() {
290                return Some(hash.clone());
291            }
292            None
293        })
294    }
295
296    /// Returns `true` if the ink! attribute contains the `payable` argument.
297    pub fn is_payable(&self) -> bool {
298        self.args()
299            .any(|arg| matches!(arg.kind(), AttributeArg::Payable))
300    }
301
302    /// Returns `true` if the ink! attribute contains the `default` argument.
303    pub fn is_default(&self) -> bool {
304        self.args()
305            .any(|arg| matches!(arg.kind(), AttributeArg::Default))
306    }
307
308    /// Returns `true` if the ink! attribute contains the wildcard selector.
309    pub fn has_wildcard_selector(&self) -> bool {
310        self.args().any(|arg| {
311            matches!(
312                arg.kind(),
313                AttributeArg::Selector(SelectorOrWildcard::Wildcard)
314            )
315        })
316    }
317
318    /// Returns `true` if the ink! attribute contains the `anonymous` argument.
319    pub fn is_anonymous(&self) -> bool {
320        self.args()
321            .any(|arg| matches!(arg.kind(), AttributeArg::Anonymous))
322    }
323
324    /// Returns `false` if the ink! attribute contains the `handle_status = false`
325    /// argument.
326    ///
327    /// Otherwise returns `true`.
328    pub fn is_handle_status(&self) -> bool {
329        !self
330            .args()
331            .any(|arg| matches!(arg.kind(), AttributeArg::HandleStatus(false)))
332    }
333}
334
335/// An ink! specific attribute argument.
336#[derive(Debug, Clone, PartialEq, Eq, Hash)]
337pub struct AttributeFrag {
338    ast: ast::Meta,
339    arg: AttributeArg,
340}
341
342impl AttributeFrag {
343    /// Returns a shared reference to the attribute argument kind.
344    pub fn kind(&self) -> &AttributeArg {
345        &self.arg
346    }
347}
348
349impl ToTokens for AttributeFrag {
350    fn to_tokens(&self, tokens: &mut TokenStream2) {
351        self.ast.to_tokens(tokens)
352    }
353}
354
355/// The kind of an ink! attribute argument.
356#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357pub enum AttributeArgKind {
358    /// `#[ink(storage)]`
359    Storage,
360    /// `#[ink(event)]`
361    Event,
362    /// `#[ink(anonymous)]`
363    Anonymous,
364    /// `#[ink(message)]`
365    Message,
366    /// `#[ink(constructor)]`
367    Constructor,
368    /// `#[ink(payable)]`
369    Payable,
370    /// `#[ink(default)]`
371    Default,
372    /// `#[ink(selector = _)]`
373    /// `#[ink(selector = 0xDEADBEEF)]`
374    Selector,
375    /// `#[ink(signature_topic =
376    /// "325c98ff66bd0d9d1c10789ae1f2a17bdfb2dcf6aa3d8092669afafdef1cb72d")]`
377    SignatureTopicArg,
378    /// `#[ink(function = N: u16)]`
379    Function,
380    /// `#[ink(namespace = "my_namespace")]`
381    Namespace,
382    /// `#[ink(impl)]`
383    Implementation,
384    /// `#[ink(handle_status = flag: bool)]`
385    HandleStatus,
386}
387
388/// An ink! specific attribute flag.
389#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
390pub enum AttributeArg {
391    /// `#[ink(storage)]`
392    ///
393    /// Applied on `struct` types in order to flag them for being the
394    /// contract's storage definition.
395    Storage,
396    /// `#[ink(event)]`
397    ///
398    /// Applied on `struct` types in order to flag them for being an ink! event.
399    Event,
400    /// `#[ink(anonymous)]`
401    ///
402    /// Applied on `struct` event types in order to flag them as anonymous.
403    /// Anonymous events have similar semantics as in Solidity in that their
404    /// event signature won't be included in their event topics serialization
405    /// to reduce event emitting overhead. This is especially useful for user
406    /// defined events.
407    Anonymous,
408    /// `#[ink(message)]`
409    ///
410    /// Applied on `&self` or `&mut self` methods to flag them for being an ink!
411    /// exported message.
412    Message,
413    /// `#[ink(constructor)]`
414    ///
415    /// Applied on inherent methods returning `Self` to flag them for being ink!
416    /// exported contract constructors.
417    Constructor,
418    /// `#[ink(payable)]`
419    ///
420    /// Applied on ink! constructors or messages in order to specify that they
421    /// can receive funds from callers.
422    Payable,
423    /// Applied on ink! constructors or messages in order to indicate
424    /// they are default.
425    Default,
426    /// Can be either one of:
427    ///
428    /// - `#[ink(selector = 0xDEADBEEF)]` Applied on ink! constructors or messages to
429    ///   manually control their selectors.
430    /// - `#[ink(selector = _)]` Applied on ink! messages to define a fallback messages
431    ///   that is invoked if no other ink! message matches a given selector.
432    Selector(SelectorOrWildcard),
433    /// `#[ink(signature_topic =
434    /// "325c98ff66bd0d9d1c10789ae1f2a17bdfb2dcf6aa3d8092669afafdef1cb72d")]`
435    SignatureTopic(String),
436    /// `#[ink(namespace = "my_namespace")]`
437    ///
438    /// Applied on ink! trait implementation blocks to disambiguate other trait
439    /// implementation blocks with equal names.
440    Namespace(Namespace),
441    /// `#[ink(impl)]`
442    ///
443    /// This attribute supports a niche case that is rarely needed.
444    ///
445    /// Can be applied on ink! implementation blocks in order to make ink! aware
446    /// of them. This is useful if such an implementation block does not contain
447    /// any other ink! attributes, so it would be flagged by ink! as a Rust item.
448    /// Adding `#[ink(impl)]` on such implementation blocks makes them treated
449    /// as ink! implementation blocks thus allowing to access the environment, etc..
450    /// Note that ink! messages and constructors still need to be explicitly
451    /// flagged as such.
452    Implementation,
453    /// `#[ink(function = N: u16)]`
454    ///
455    /// Applies on ink! chain extension method to set their `func_id` parameter.
456    /// Every chain extension method must have exactly one ink! `function` attribute.
457    ///
458    /// Used by the `#[ink::chain_extension]` procedural macro.
459    Function(FunctionId),
460    /// `#[ink(handle_status = flag: bool)]`
461    ///
462    /// Used by the `#[ink::chain_extension]` procedural macro.
463    ///
464    /// Default value: `true`
465    HandleStatus(bool),
466}
467
468impl core::fmt::Display for AttributeArgKind {
469    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
470        match self {
471            Self::Storage => write!(f, "storage"),
472            Self::Event => write!(f, "event"),
473            Self::Anonymous => write!(f, "anonymous"),
474            Self::Message => write!(f, "message"),
475            Self::Constructor => write!(f, "constructor"),
476            Self::Payable => write!(f, "payable"),
477            Self::Selector => {
478                write!(f, "selector = S:[u8; 4] || _")
479            }
480            Self::SignatureTopicArg => {
481                write!(f, "signature_topic = S:[u8; 32]")
482            }
483            Self::Function => {
484                write!(f, "function = N:u16)")
485            }
486            Self::Namespace => {
487                write!(f, "namespace = N:string")
488            }
489            Self::Implementation => write!(f, "impl"),
490            Self::HandleStatus => write!(f, "handle_status"),
491            Self::Default => write!(f, "default"),
492        }
493    }
494}
495
496impl AttributeArg {
497    /// Returns the kind of the ink! attribute argument.
498    pub fn kind(&self) -> AttributeArgKind {
499        match self {
500            Self::Storage => AttributeArgKind::Storage,
501            Self::Event => AttributeArgKind::Event,
502            Self::Anonymous => AttributeArgKind::Anonymous,
503            Self::Message => AttributeArgKind::Message,
504            Self::Constructor => AttributeArgKind::Constructor,
505            Self::Payable => AttributeArgKind::Payable,
506            Self::Selector(_) => AttributeArgKind::Selector,
507            Self::SignatureTopic(_) => AttributeArgKind::SignatureTopicArg,
508            Self::Function(_) => AttributeArgKind::Function,
509            Self::Namespace(_) => AttributeArgKind::Namespace,
510            Self::Implementation => AttributeArgKind::Implementation,
511            Self::HandleStatus(_) => AttributeArgKind::HandleStatus,
512            Self::Default => AttributeArgKind::Default,
513        }
514    }
515}
516
517impl core::fmt::Display for AttributeArg {
518    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
519        match self {
520            Self::Storage => write!(f, "storage"),
521            Self::Event => write!(f, "event"),
522            Self::Anonymous => write!(f, "anonymous"),
523            Self::Message => write!(f, "message"),
524            Self::Constructor => write!(f, "constructor"),
525            Self::Payable => write!(f, "payable"),
526            Self::Selector(selector) => core::fmt::Display::fmt(&selector, f),
527            Self::SignatureTopic(hash) => {
528                write!(f, "signature_topic = {:?}", hash)
529            }
530            Self::Function(function) => {
531                write!(f, "function = {:?}", function.into_u16())
532            }
533            Self::Namespace(namespace) => {
534                write!(f, "namespace = {:?}", namespace.as_bytes())
535            }
536            Self::Implementation => write!(f, "impl"),
537            Self::HandleStatus(value) => write!(f, "handle_status = {value:?}"),
538            Self::Default => write!(f, "default"),
539        }
540    }
541}
542
543/// Either a wildcard selector or a specified selector.
544#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
545pub enum SelectorOrWildcard {
546    /// A wildcard selector. If no other selector matches, the message/constructor
547    /// annotated with the wildcard selector will be invoked.
548    Wildcard,
549    /// A user provided selector.
550    UserProvided(Selector),
551}
552
553impl SelectorOrWildcard {
554    /// Create a new `SelectorOrWildcard::Selector` from the supplied bytes.
555    fn selector(bytes: [u8; 4]) -> Self {
556        SelectorOrWildcard::UserProvided(Selector::from(bytes))
557    }
558
559    /// The selector of the wildcard complement message.
560    pub fn wildcard_complement() -> Self {
561        Self::selector(IIP2_WILDCARD_COMPLEMENT_SELECTOR)
562    }
563}
564
565impl TryFrom<&ast::MetaValue> for SelectorOrWildcard {
566    type Error = syn::Error;
567
568    fn try_from(value: &ast::MetaValue) -> Result<Self, Self::Error> {
569        match value {
570            ast::MetaValue::Lit(lit) => {
571                if let syn::Lit::Str(_) = lit {
572                    return Err(format_err_spanned!(
573                        lit,
574                        "#[ink(selector = ..)] attributes with string inputs are deprecated. \
575                        use an integer instead, e.g. #[ink(selector = 1)] or #[ink(selector = 0xC0DECAFE)]."
576                    ));
577                }
578                if let syn::Lit::Int(lit_int) = lit {
579                    let selector_u32 = lit_int.base10_parse::<u32>()
580                        .map_err(|error| {
581                            format_err_spanned!(
582                                lit_int,
583                                "selector value out of range. selector must be a valid `u32` integer: {}",
584                                error
585                            )
586                        })?;
587                    let selector = Selector::from(selector_u32.to_be_bytes());
588                    return Ok(SelectorOrWildcard::UserProvided(selector))
589                }
590                Err(format_err_spanned!(
591                    value,
592                    "expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"
593                ))
594            }
595            ast::MetaValue::Symbol(symbol) => {
596                match symbol {
597                    ast::Symbol::Underscore(_) => Ok(SelectorOrWildcard::Wildcard),
598                    ast::Symbol::AtSign(_) => Ok(SelectorOrWildcard::wildcard_complement()),
599                }
600            }
601            ast::MetaValue::Path(path) => {
602                Err(format_err_spanned!(
603                    path,
604                    "unexpected path for `selector` argument, expected a 4-digit hexcode or one of \
605                    the wildcard symbols: `_` or `@`"
606                ))
607            }
608        }
609    }
610}
611
612impl core::fmt::Display for SelectorOrWildcard {
613    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
614        match self {
615            Self::UserProvided(selector) => core::fmt::Debug::fmt(&selector, f),
616            Self::Wildcard => write!(f, "_"),
617        }
618    }
619}
620
621/// An ink! namespace applicable to a trait implementation block.
622#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
623pub struct Namespace {
624    /// The underlying bytes.
625    bytes: Vec<u8>,
626}
627
628impl TryFrom<&ast::MetaValue> for Namespace {
629    type Error = syn::Error;
630
631    fn try_from(value: &ast::MetaValue) -> Result<Self, Self::Error> {
632        if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = value {
633            let argument = lit_str.value();
634            syn::parse_str::<syn::Ident>(&argument).map_err(|_error| {
635                format_err_spanned!(
636                    lit_str,
637                    "encountered invalid Rust identifier for namespace argument",
638                )
639            })?;
640            Ok(Namespace::from(argument.into_bytes()))
641        } else {
642            Err(format_err_spanned!(
643                value,
644                "expected string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]",
645            ))
646        }
647    }
648}
649
650impl From<Vec<u8>> for Namespace {
651    fn from(bytes: Vec<u8>) -> Self {
652        Self { bytes }
653    }
654}
655
656impl Namespace {
657    /// Returns the namespace as bytes.
658    pub fn as_bytes(&self) -> &[u8] {
659        &self.bytes
660    }
661}
662
663/// Returns `true` if the given iterator yields at least one attribute of the form
664/// `#[ink(...)]` or `#[ink]`.
665///
666/// # Note
667///
668/// This does not check at this point whether the ink! attribute is valid since
669/// this check is optimized for efficiency.
670pub fn contains_ink_attributes<'a, I>(attrs: I) -> bool
671where
672    I: IntoIterator<Item = &'a syn::Attribute>,
673{
674    attrs.into_iter().any(|attr| attr.path().is_ident("ink"))
675}
676
677/// Returns the first valid ink! attribute, if any.
678///
679/// Returns `None` if there are no ink! attributes.
680///
681/// # Errors
682///
683/// Returns an error if the first ink! attribute is invalid.
684pub fn first_ink_attribute<'a, I>(
685    attrs: I,
686) -> Result<Option<ir::InkAttribute>, syn::Error>
687where
688    I: IntoIterator<Item = &'a syn::Attribute>,
689{
690    let first = attrs.into_iter().find(|attr| attr.path().is_ident("ink"));
691    match first {
692        None => Ok(None),
693        Some(ink_attr) => InkAttribute::try_from(ink_attr.clone()).map(Some),
694    }
695}
696
697/// Partitions the given attributes into ink! specific and non-ink! specific attributes.
698///
699/// # Error
700///
701/// Returns an error if some ink! specific attributes could not be successfully parsed.
702pub fn partition_attributes<I>(
703    attrs: I,
704) -> Result<(Vec<InkAttribute>, Vec<syn::Attribute>), syn::Error>
705where
706    I: IntoIterator<Item = syn::Attribute>,
707{
708    use either::Either;
709    use itertools::Itertools as _;
710    let (ink_attrs, others) = attrs
711        .into_iter()
712        .map(<Attribute as TryFrom<_>>::try_from)
713        .collect::<Result<Vec<Attribute>, syn::Error>>()?
714        .into_iter()
715        .partition_map(|attr| {
716            match attr {
717                Attribute::Ink(ink_attr) => Either::Left(ink_attr),
718                Attribute::Other(other_attr) => Either::Right(other_attr),
719            }
720        });
721    Attribute::ensure_no_duplicate_attrs(&ink_attrs)?;
722    Ok((ink_attrs, others))
723}
724
725/// Sanitizes the given attributes.
726///
727/// This partitions the attributes into ink! and non-ink! attributes.
728/// All ink! attributes are normalized, they are checked to have a valid first
729/// ink! attribute argument and no conflicts given the conflict predicate.
730///
731/// Returns the partitioned ink! and non-ink! attributes.
732///
733/// # Parameters
734///
735/// The `is_conflicting_attr` closure returns `Ok` if the attribute does not conflict,
736/// returns `Err(None)` if the attribute conflicts but without providing further reasoning
737/// and `Err(Some(reason))` if the attribute conflicts given additional context
738/// information.
739///
740/// # Errors
741///
742/// - If there are invalid ink! attributes.
743/// - If there are duplicate ink! attributes.
744/// - If the first ink! attribute is not matching the expected.
745/// - If there are conflicting ink! attributes.
746/// - if there are no ink! attributes.
747pub fn sanitize_attributes<I, C>(
748    parent_span: Span,
749    attrs: I,
750    is_valid_first: &ir::AttributeArgKind,
751    is_conflicting_attr: C,
752) -> Result<(InkAttribute, Vec<syn::Attribute>), syn::Error>
753where
754    I: IntoIterator<Item = syn::Attribute>,
755    C: FnMut(&ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
756{
757    let (ink_attrs, other_attrs) = ir::partition_attributes(attrs)?;
758    let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
759        err.into_combine(format_err!(parent_span, "at this invocation",))
760    })?;
761    normalized.ensure_first(is_valid_first).map_err(|err| {
762        err.into_combine(format_err!(
763            parent_span,
764            "expected {} as first ink! attribute argument",
765            is_valid_first,
766        ))
767    })?;
768    normalized.ensure_no_conflicts(is_conflicting_attr)?;
769    Ok((normalized, other_attrs))
770}
771
772/// Sanitizes the given optional attributes.
773///
774/// This partitions the attributes into ink! and non-ink! attributes.
775/// If there are ink! attributes they are normalized and deduplicated.
776/// Also checks to guard against conflicting ink! attributes are provided.
777///
778/// Returns the optional partitioned ink! and non-ink! attributes.
779///
780/// # Parameters
781///
782/// The `is_conflicting_attr` closure returns `Ok` if the attribute does not conflict,
783/// returns `Err(None)` if the attribute conflicts but without providing further reasoning
784/// and `Err(Some(reason))` if the attribute conflicts given additional context
785/// information.
786///
787/// # Errors
788///
789/// - If there are invalid ink! attributes.
790/// - If there are duplicate ink! attributes.
791/// - If there are conflicting ink! attributes.
792pub fn sanitize_optional_attributes<I, C>(
793    parent_span: Span,
794    attrs: I,
795    is_conflicting_attr: C,
796) -> Result<(Option<InkAttribute>, Vec<syn::Attribute>), syn::Error>
797where
798    I: IntoIterator<Item = syn::Attribute>,
799    C: FnMut(&ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
800{
801    let (ink_attrs, rust_attrs) = ir::partition_attributes(attrs)?;
802    if ink_attrs.is_empty() {
803        return Ok((None, rust_attrs));
804    }
805    let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
806        err.into_combine(format_err!(parent_span, "at this invocation",))
807    })?;
808    normalized.ensure_no_conflicts(is_conflicting_attr)?;
809    Ok((Some(normalized), rust_attrs))
810}
811
812impl Attribute {
813    /// Returns `Ok` if the given iterator yields no duplicate ink! attributes.
814    ///
815    /// # Errors
816    ///
817    /// If the given iterator yields duplicate ink! attributes.
818    /// Note: Duplicate non-ink! attributes are fine.
819    fn ensure_no_duplicate_attrs<'a, I>(attrs: I) -> Result<(), syn::Error>
820    where
821        I: IntoIterator<Item = &'a InkAttribute>,
822    {
823        use std::collections::HashSet;
824        let mut seen: HashSet<&InkAttribute> = HashSet::new();
825        for attr in attrs.into_iter() {
826            if let Some(seen) = seen.get(attr) {
827                use crate::error::ExtError as _;
828                return Err(format_err!(
829                    attr.span(),
830                    "encountered duplicate ink! attribute"
831                )
832                .into_combine(format_err!(seen.span(), "first ink! attribute here")));
833            }
834            seen.insert(attr);
835        }
836        Ok(())
837    }
838}
839
840impl TryFrom<syn::Attribute> for Attribute {
841    type Error = syn::Error;
842
843    fn try_from(attr: syn::Attribute) -> Result<Self, Self::Error> {
844        if attr.path().is_ident("ink") {
845            return <InkAttribute as TryFrom<_>>::try_from(attr).map(Into::into);
846        }
847        Ok(Attribute::Other(attr))
848    }
849}
850
851impl From<InkAttribute> for Attribute {
852    fn from(ink_attribute: InkAttribute) -> Self {
853        Attribute::Ink(ink_attribute)
854    }
855}
856
857impl TryFrom<syn::Attribute> for InkAttribute {
858    type Error = syn::Error;
859
860    fn try_from(attr: syn::Attribute) -> Result<Self, Self::Error> {
861        if !attr.path().is_ident("ink") {
862            return Err(format_err_spanned!(attr, "unexpected non-ink! attribute"));
863        }
864
865        let args: Vec<_> = attr
866            .parse_args_with(Punctuated::<AttributeFrag, Token![,]>::parse_terminated)?
867            .into_iter()
868            .collect();
869
870        Self::ensure_no_duplicate_args(&args)?;
871        if args.is_empty() {
872            return Err(format_err_spanned!(
873                attr,
874                "encountered unsupported empty ink! attribute"
875            ));
876        }
877        Ok(InkAttribute { args })
878    }
879}
880
881impl InkAttribute {
882    /// Ensures that there are no conflicting ink! attribute arguments in `self`.
883    ///
884    /// The given `is_conflicting` describes for every ink! attribute argument
885    /// found in `self` if it is in conflict.
886    ///
887    /// # Parameters
888    ///
889    /// The `is_conflicting_attr` closure returns `Ok` if the attribute does not conflict,
890    /// returns `Err(None)` if the attribute conflicts but without providing further
891    /// reasoning and `Err(Some(reason))` if the attribute conflicts given additional
892    /// context information.
893    pub fn ensure_no_conflicts<'a, P>(
894        &'a self,
895        mut is_conflicting: P,
896    ) -> Result<(), syn::Error>
897    where
898        P: FnMut(&'a ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
899    {
900        let mut err: Option<syn::Error> = None;
901        for arg in self.args() {
902            if let Err(reason) = is_conflicting(arg) {
903                let conflict_err = format_err!(
904                    arg.span(),
905                    "encountered conflicting ink! attribute argument",
906                );
907                match &mut err {
908                    Some(err) => {
909                        err.combine(conflict_err);
910                    }
911                    None => {
912                        err = Some(conflict_err);
913                    }
914                }
915                if let Some(reason) = reason {
916                    err.as_mut()
917                        .expect("must be `Some` at this point")
918                        .combine(reason);
919                }
920            }
921        }
922        if let Some(err) = err {
923            return Err(err);
924        }
925        Ok(())
926    }
927}
928
929impl Parse for AttributeFrag {
930    fn parse(input: ParseStream) -> syn::Result<Self> {
931        let ast: ast::Meta = input.parse()?;
932
933        let arg = match &ast {
934            ast::Meta::NameValue(name_value) => {
935                let ident = name_value.name.get_ident().ok_or_else(|| {
936                    format_err_spanned!(
937                        name_value.name,
938                        "expected identifier for ink! attribute argument",
939                    )
940                })?;
941                match ident.to_string().as_str() {
942                    "selector" => {
943                        SelectorOrWildcard::try_from(&name_value.value)
944                            .map(AttributeArg::Selector)
945                    }
946                    "namespace" => {
947                        Namespace::try_from(&name_value.value)
948                            .map(AttributeArg::Namespace)
949                    }
950                    "signature_topic" => {
951                        if let Some(hash) = name_value.value.as_string() {
952                            Ok(AttributeArg::SignatureTopic(hash))
953                        } else {
954                            Err(format_err_spanned!(
955                                name_value.value,
956                                "expected String type for `S` in #[ink(signature_topic = S)]",
957                            ))
958                        }
959                    }
960                    "function" => {
961                        if let Some(lit_int) = name_value.value.as_lit_int() {
962                            let id = lit_int.base10_parse::<u16>()
963                                .map_err(|error| {
964                                    format_err_spanned!(
965                                        lit_int,
966                                        "could not parse `N` in `#[ink(function = N)]` into a `u16` integer: {}", error)
967                                })?;
968                            Ok(AttributeArg::Function(FunctionId::from_u16(id)))
969                        } else {
970                            Err(format_err_spanned!(
971                                name_value.value,
972                                "expected `u16` integer type for `N` in #[ink(function = N)]",
973                            ))
974                        }
975                    }
976                    "handle_status" => {
977                        if let Some(value) = name_value.value.as_bool() {
978                            Ok(AttributeArg::HandleStatus(value))
979                        } else {
980                            Err(format_err_spanned!(
981                                name_value.value,
982                                "expected `bool` value type for `flag` in #[ink(handle_status = flag)]",
983                            ))
984                        }
985                    }
986                    _ => {
987                        Err(format_err_spanned!(
988                            ident,
989                            "encountered unknown ink! attribute argument: {}",
990                            ident
991                        ))
992                    }
993                }
994            }
995            ast::Meta::Path(path) => {
996                let ident = path.get_ident().ok_or_else(|| {
997                    format_err_spanned!(
998                        path,
999                        "expected identifier for ink! attribute argument",
1000                    )
1001                })?;
1002                match ident.to_string().as_str() {
1003                    "storage" => Ok(AttributeArg::Storage),
1004                    "message" => Ok(AttributeArg::Message),
1005                    "constructor" => Ok(AttributeArg::Constructor),
1006                    "event" => Ok(AttributeArg::Event),
1007                    "anonymous" => Ok(AttributeArg::Anonymous),
1008                    "payable" => Ok(AttributeArg::Payable),
1009                    "default" => Ok(AttributeArg::Default),
1010                    "impl" => Ok(AttributeArg::Implementation),
1011                    _ => match ident.to_string().as_str() {
1012                        "function" => Err(format_err_spanned!(
1013                            path,
1014                            "encountered #[ink(function)] that is missing its `id` parameter. \
1015                            Did you mean #[ink(function = id: u16)] ?"
1016                        )),
1017                        "handle_status" => Err(format_err_spanned!(
1018                            path,
1019                           "encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \
1020                            Did you mean #[ink(handle_status = flag: bool)] ?"
1021                        )),
1022                        "namespace" => Err(format_err_spanned!(
1023                            path,
1024                           "encountered #[ink(namespace)] that is missing its string parameter. \
1025                            Did you mean #[ink(namespace = name: str)] ?"
1026                        )),
1027                        "selector" => Err(format_err_spanned!(
1028                            path,
1029                           "encountered #[ink(selector)] that is missing its u32 parameter. \
1030                            Did you mean #[ink(selector = value: u32)] ?"
1031                        )),
1032                        _ => Err(format_err_spanned!(
1033                            path,
1034                            "encountered unknown ink! attribute argument: {}",
1035                            ident
1036                        )),
1037                    },
1038                }
1039            }
1040        }?;
1041
1042        Ok(Self { ast, arg })
1043    }
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048    use super::*;
1049
1050    #[test]
1051    fn contains_ink_attributes_works() {
1052        assert!(!contains_ink_attributes(&[]));
1053        assert!(contains_ink_attributes(&[syn::parse_quote! { #[ink] }]));
1054        assert!(contains_ink_attributes(&[syn::parse_quote! { #[ink(..)] }]));
1055        assert!(contains_ink_attributes(&[
1056            syn::parse_quote! { #[inline] },
1057            syn::parse_quote! { #[likely] },
1058            syn::parse_quote! { #[ink(storage)] },
1059        ]));
1060        assert!(!contains_ink_attributes(&[
1061            syn::parse_quote! { #[inline] },
1062            syn::parse_quote! { #[likely] },
1063        ]));
1064    }
1065
1066    /// Asserts that the given input yields the expected first argument or the
1067    /// expected error string.
1068    ///
1069    /// # Note
1070    ///
1071    /// Can be used to assert against the success and failure path.
1072    fn assert_first_ink_attribute(
1073        input: &[syn::Attribute],
1074        expected: Result<Option<Vec<ir::AttributeArg>>, &'static str>,
1075    ) {
1076        assert_eq!(
1077            first_ink_attribute(input)
1078                .map(|maybe_attr: Option<ir::InkAttribute>| {
1079                    maybe_attr.map(|attr: ir::InkAttribute| {
1080                        attr.args.into_iter().map(|arg| arg.arg).collect::<Vec<_>>()
1081                    })
1082                })
1083                .map_err(|err| err.to_string()),
1084            expected.map_err(ToString::to_string),
1085        )
1086    }
1087
1088    #[test]
1089    fn first_ink_attribute_works() {
1090        assert_first_ink_attribute(&[], Ok(None));
1091        assert_first_ink_attribute(
1092            &[syn::parse_quote! { #[ink(storage)] }],
1093            Ok(Some(vec![AttributeArg::Storage])),
1094        );
1095        assert_first_ink_attribute(
1096            &[syn::parse_quote! { #[ink(invalid)] }],
1097            Err("encountered unknown ink! attribute argument: invalid"),
1098        );
1099    }
1100
1101    mod test {
1102        use crate::ir;
1103
1104        /// Mock for `ir::Attribute` to improve the ability to test.
1105        #[derive(Debug, PartialEq, Eq)]
1106        #[allow(clippy::large_enum_variant)] // todo
1107        pub enum Attribute {
1108            Ink(Vec<ir::AttributeArg>),
1109            Other(syn::Attribute),
1110        }
1111
1112        impl From<ir::Attribute> for Attribute {
1113            fn from(attr: ir::Attribute) -> Self {
1114                match attr {
1115                    ir::Attribute::Ink(ink_attr) => {
1116                        Self::Ink(
1117                            ink_attr
1118                                .args
1119                                .into_iter()
1120                                .map(|arg| arg.arg)
1121                                .collect::<Vec<_>>(),
1122                        )
1123                    }
1124                    ir::Attribute::Other(other_attr) => Self::Other(other_attr),
1125                }
1126            }
1127        }
1128
1129        impl From<ir::InkAttribute> for Attribute {
1130            fn from(ink_attr: ir::InkAttribute) -> Self {
1131                Attribute::from(ir::Attribute::Ink(ink_attr))
1132            }
1133        }
1134
1135        /// Mock for `ir::InkAttribute` to improve the ability to test.
1136        #[derive(Debug, PartialEq, Eq)]
1137        pub struct InkAttribute {
1138            args: Vec<ir::AttributeArg>,
1139        }
1140
1141        impl From<ir::InkAttribute> for InkAttribute {
1142            fn from(ink_attr: ir::InkAttribute) -> Self {
1143                Self {
1144                    args: ink_attr
1145                        .args
1146                        .into_iter()
1147                        .map(|arg| arg.arg)
1148                        .collect::<Vec<_>>(),
1149                }
1150            }
1151        }
1152
1153        impl<I> From<I> for InkAttribute
1154        where
1155            I: IntoIterator<Item = ir::AttributeArg>,
1156        {
1157            fn from(args: I) -> Self {
1158                Self {
1159                    args: args.into_iter().collect::<Vec<_>>(),
1160                }
1161            }
1162        }
1163    }
1164
1165    /// Asserts that the given [`syn::Attribute`] is converted into the expected
1166    /// [`ir::Attribute`] or yields the expected error message.
1167    fn assert_attribute_try_from(
1168        input: syn::Attribute,
1169        expected: Result<test::Attribute, &'static str>,
1170    ) {
1171        assert_eq!(
1172            <ir::Attribute as TryFrom<_>>::try_from(input)
1173                .map(test::Attribute::from)
1174                .map_err(|err| err.to_string()),
1175            expected.map_err(ToString::to_string),
1176        )
1177    }
1178
1179    #[test]
1180    fn storage_works() {
1181        assert_attribute_try_from(
1182            syn::parse_quote! {
1183                #[ink(storage)]
1184            },
1185            Ok(test::Attribute::Ink(vec![AttributeArg::Storage])),
1186        );
1187    }
1188
1189    /// This tests that `#[ink(impl)]` works which can be non-trivial since
1190    /// `impl` is also a Rust keyword.
1191    #[test]
1192    fn impl_works() {
1193        assert_attribute_try_from(
1194            syn::parse_quote! {
1195                #[ink(impl)]
1196            },
1197            Ok(test::Attribute::Ink(vec![AttributeArg::Implementation])),
1198        );
1199    }
1200
1201    #[test]
1202    fn selector_works() {
1203        assert_attribute_try_from(
1204            syn::parse_quote! {
1205                #[ink(selector = 42)]
1206            },
1207            Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
1208                SelectorOrWildcard::UserProvided(Selector::from([0, 0, 0, 42])),
1209            )])),
1210        );
1211        assert_attribute_try_from(
1212            syn::parse_quote! {
1213                #[ink(selector = 0xDEADBEEF)]
1214            },
1215            Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
1216                SelectorOrWildcard::selector([0xDE, 0xAD, 0xBE, 0xEF]),
1217            )])),
1218        );
1219    }
1220
1221    #[test]
1222    fn wildcard_selector_works() {
1223        assert_attribute_try_from(
1224            syn::parse_quote! {
1225                #[ink(selector = _)]
1226            },
1227            Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
1228                SelectorOrWildcard::Wildcard,
1229            )])),
1230        );
1231    }
1232
1233    #[test]
1234    fn selector_negative_number() {
1235        assert_attribute_try_from(
1236            syn::parse_quote! {
1237                #[ink(selector = -1)]
1238            },
1239            Err(
1240                "selector value out of range. selector must be a valid `u32` integer: \
1241                invalid digit found in string",
1242            ),
1243        );
1244    }
1245
1246    #[test]
1247    fn selector_out_of_range() {
1248        assert_attribute_try_from(
1249            syn::parse_quote! {
1250                #[ink(selector = 0xFFFF_FFFF_FFFF_FFFF)]
1251            },
1252            Err(
1253                "selector value out of range. \
1254                selector must be a valid `u32` integer: number too large to fit in target type"
1255            ),
1256        );
1257    }
1258
1259    #[test]
1260    fn selector_invalid_type() {
1261        assert_attribute_try_from(
1262            syn::parse_quote! {
1263                #[ink(selector = true)]
1264            },
1265            Err("expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"),
1266        );
1267    }
1268
1269    #[test]
1270    fn default_works() {
1271        assert_attribute_try_from(
1272            syn::parse_quote! {
1273                #[ink(default)]
1274            },
1275            Ok(test::Attribute::Ink(vec![AttributeArg::Default])),
1276        )
1277    }
1278
1279    #[test]
1280    fn namespace_works() {
1281        assert_attribute_try_from(
1282            syn::parse_quote! {
1283                #[ink(namespace = "my_namespace")]
1284            },
1285            Ok(test::Attribute::Ink(vec![AttributeArg::Namespace(
1286                Namespace::from("my_namespace".to_string().into_bytes()),
1287            )])),
1288        );
1289    }
1290
1291    #[test]
1292    fn namespace_invalid_identifier() {
1293        assert_attribute_try_from(
1294            syn::parse_quote! {
1295                #[ink(namespace = "::invalid_identifier")]
1296            },
1297            Err("encountered invalid Rust identifier for namespace argument"),
1298        );
1299    }
1300
1301    #[test]
1302    fn namespace_invalid_type() {
1303        assert_attribute_try_from(
1304            syn::parse_quote! {
1305                #[ink(namespace = 42)]
1306            },
1307            Err("expected string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]"),
1308        );
1309    }
1310
1311    #[test]
1312    fn namespace_missing_parameter() {
1313        assert_attribute_try_from(
1314            syn::parse_quote! {
1315                #[ink(namespace)]
1316            },
1317            Err(
1318                "encountered #[ink(namespace)] that is missing its string parameter. \
1319                Did you mean #[ink(namespace = name: str)] ?",
1320            ),
1321        );
1322    }
1323
1324    #[test]
1325    fn extension_works() {
1326        assert_attribute_try_from(
1327            syn::parse_quote! {
1328                #[ink(function = 42)]
1329            },
1330            Ok(test::Attribute::Ink(vec![AttributeArg::Function(
1331                FunctionId::from_u16(42),
1332            )])),
1333        );
1334    }
1335
1336    #[test]
1337    fn extension_invalid_value_type() {
1338        assert_attribute_try_from(
1339            syn::parse_quote! {
1340                #[ink(function = "string")]
1341            },
1342            Err("expected `u16` integer type for `N` in #[ink(function = N)]"),
1343        );
1344    }
1345
1346    #[test]
1347    fn extension_negative_integer() {
1348        assert_attribute_try_from(
1349            syn::parse_quote! {
1350                #[ink(function = -1)]
1351            },
1352            Err("could not parse `N` in `#[ink(function = N)]` into a `u16` integer: invalid digit found in string")
1353        );
1354    }
1355
1356    #[test]
1357    fn extension_too_big_integer() {
1358        let max_u32_plus_1 = (u32::MAX as u64) + 1;
1359        assert_attribute_try_from(
1360            syn::parse_quote! {
1361                #[ink(function = #max_u32_plus_1)]
1362            },
1363            Err("could not parse `N` in `#[ink(function = N)]` into a `u16` integer: number too large to fit in target type"),
1364        );
1365    }
1366
1367    #[test]
1368    fn extension_missing_parameter() {
1369        assert_attribute_try_from(
1370            syn::parse_quote! {
1371                #[ink(function)]
1372            },
1373            Err(
1374                "encountered #[ink(function)] that is missing its `id` parameter. \
1375                Did you mean #[ink(function = id: u16)] ?",
1376            ),
1377        );
1378    }
1379
1380    #[test]
1381    fn handle_status_works() {
1382        fn expected_ok(value: bool) -> Result<test::Attribute, &'static str> {
1383            Ok(test::Attribute::Ink(vec![AttributeArg::HandleStatus(
1384                value,
1385            )]))
1386        }
1387        assert_attribute_try_from(
1388            syn::parse_quote! {
1389                #[ink(handle_status = true)]
1390            },
1391            expected_ok(true),
1392        );
1393        assert_attribute_try_from(
1394            syn::parse_quote! {
1395                #[ink(handle_status = false)]
1396            },
1397            expected_ok(false),
1398        );
1399    }
1400
1401    #[test]
1402    fn handle_status_missing_parameter() {
1403        assert_attribute_try_from(
1404            syn::parse_quote! {
1405                #[ink(handle_status)]
1406            },
1407            Err(
1408                "encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \
1409                Did you mean #[ink(handle_status = flag: bool)] ?",
1410            ),
1411        );
1412    }
1413
1414    #[test]
1415    fn handle_status_invalid_parameter_type() {
1416        assert_attribute_try_from(
1417            syn::parse_quote! {
1418                #[ink(handle_status = "string")]
1419            },
1420            Err("expected `bool` value type for `flag` in #[ink(handle_status = flag)]"),
1421        );
1422    }
1423
1424    #[test]
1425    fn compound_mixed_works() {
1426        assert_attribute_try_from(
1427            syn::parse_quote! {
1428                #[ink(message, namespace = "my_namespace")]
1429            },
1430            Ok(test::Attribute::Ink(vec![
1431                AttributeArg::Message,
1432                AttributeArg::Namespace(Namespace::from(
1433                    "my_namespace".to_string().into_bytes(),
1434                )),
1435            ])),
1436        )
1437    }
1438
1439    #[test]
1440    fn compound_simple_works() {
1441        assert_attribute_try_from(
1442            syn::parse_quote! {
1443                #[ink(
1444                    storage,
1445                    message,
1446                    constructor,
1447                    event,
1448                    payable,
1449                    impl,
1450                )]
1451            },
1452            Ok(test::Attribute::Ink(vec![
1453                AttributeArg::Storage,
1454                AttributeArg::Message,
1455                AttributeArg::Constructor,
1456                AttributeArg::Event,
1457                AttributeArg::Payable,
1458                AttributeArg::Implementation,
1459            ])),
1460        );
1461    }
1462
1463    #[test]
1464    fn non_ink_attribute_works() {
1465        let attr: syn::Attribute = syn::parse_quote! {
1466            #[non_ink(message)]
1467        };
1468        assert_attribute_try_from(attr.clone(), Ok(test::Attribute::Other(attr)));
1469    }
1470
1471    #[test]
1472    fn empty_ink_attribute_fails() {
1473        assert_attribute_try_from(
1474            syn::parse_quote! {
1475                #[ink]
1476            },
1477            Err("expected attribute arguments in parentheses: #[ink(...)]"),
1478        );
1479        assert_attribute_try_from(
1480            syn::parse_quote! {
1481                #[ink()]
1482            },
1483            Err("encountered unsupported empty ink! attribute"),
1484        );
1485    }
1486
1487    #[test]
1488    fn duplicate_flags_fails() {
1489        assert_attribute_try_from(
1490            syn::parse_quote! {
1491                #[ink(message, message)]
1492            },
1493            Err("encountered duplicate ink! attribute arguments"),
1494        );
1495    }
1496
1497    /// Asserts that the given sequence of [`syn::Attribute`] is correctly
1498    /// partitioned into the expected tuple of ink! and non-ink! attributes
1499    /// or that the expected error is returned.
1500    fn assert_parition_attributes(
1501        input: Vec<syn::Attribute>,
1502        expected: Result<(Vec<test::InkAttribute>, Vec<syn::Attribute>), &'static str>,
1503    ) {
1504        assert_eq!(
1505            partition_attributes(input)
1506                .map(|(ink_attr, other_attr)| {
1507                    (
1508                        ink_attr
1509                            .into_iter()
1510                            .map(test::InkAttribute::from)
1511                            .collect::<Vec<_>>(),
1512                        other_attr,
1513                    )
1514                })
1515                .map_err(|err| err.to_string()),
1516            expected.map_err(ToString::to_string)
1517        );
1518    }
1519
1520    #[test]
1521    fn parition_attributes_works() {
1522        assert_parition_attributes(
1523            vec![
1524                syn::parse_quote! { #[ink(message)] },
1525                syn::parse_quote! { #[non_ink_attribute] },
1526            ],
1527            Ok((
1528                vec![test::InkAttribute::from(vec![AttributeArg::Message])],
1529                vec![syn::parse_quote! { #[non_ink_attribute] }],
1530            )),
1531        )
1532    }
1533
1534    #[test]
1535    fn parition_duplicates_fails() {
1536        assert_parition_attributes(
1537            vec![
1538                syn::parse_quote! { #[ink(message)] },
1539                syn::parse_quote! { #[ink(message)] },
1540            ],
1541            Err("encountered duplicate ink! attribute"),
1542        )
1543    }
1544    #[test]
1545    fn signature_topic_works() {
1546        let s = "11".repeat(32);
1547        assert_attribute_try_from(
1548            syn::parse_quote! {
1549                #[ink(signature_topic = #s)]
1550            },
1551            Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic(s)])),
1552        );
1553    }
1554}