ink_ir/ir/
utils.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 super::{
16    Selector,
17    SelectorAbi,
18};
19use crate::{
20    ast::{
21        self,
22        MetaNameValue,
23        MetaValue,
24    },
25    error::ExtError as _,
26};
27use proc_macro2::Span;
28use std::collections::HashMap;
29use syn::spanned::Spanned;
30
31/// Ensures that the given visibility is `pub` and otherwise returns an appropriate error.
32///
33/// # Note
34///
35/// The `name` parameter is given to improve the resulting error message. It denotes the
36/// entity which cannot have non-public visibility.
37pub fn ensure_pub_visibility(
38    name: &str,
39    parent_span: Span,
40    vis: &syn::Visibility,
41) -> Result<(), syn::Error> {
42    let bad_visibility = match vis {
43        syn::Visibility::Inherited => Some(parent_span),
44        syn::Visibility::Restricted(vis_restricted) => Some(vis_restricted.span()),
45        syn::Visibility::Public(_) => None,
46    };
47    if let Some(bad_visibility) = bad_visibility {
48        return Err(format_err!(
49            bad_visibility,
50            "non `pub` ink! {} are not supported",
51            name
52        ))
53    }
54    Ok(())
55}
56
57/// Returns a local ID unique to the ink! trait definition for the identifier.
58///
59/// # Note
60///
61/// - The returned value is equal to the selector of the message identifier.
62/// - Used from within ink! trait definitions as well as ink! trait implementation blocks.
63pub fn local_message_id(ident: &str) -> u32 {
64    let input = ident.as_bytes();
65    let selector = Selector::compute(input, SelectorAbi::Ink);
66    selector.into_be_u32()
67}
68
69/// The set of attributes that can be passed to call builder or call forwarder in the
70/// codegen.
71#[derive(Debug, PartialEq, Eq)]
72pub struct WhitelistedAttributes(pub HashMap<String, ()>);
73
74impl Default for WhitelistedAttributes {
75    fn default() -> Self {
76        Self(HashMap::from([
77            // Conditional compilation
78            ("cfg".to_string(), ()),
79            ("cfg_attr".to_string(), ()),
80            // Diagnostics
81            ("allow".to_string(), ()),
82            ("warn".to_string(), ()),
83            ("deny".to_string(), ()),
84            ("forbid".to_string(), ()),
85            ("deprecated".to_string(), ()),
86            ("must_use".to_string(), ()),
87            // Documentation
88            ("doc".to_string(), ()),
89            // Formatting
90            ("rustfmt".to_string(), ()),
91        ]))
92    }
93}
94
95impl WhitelistedAttributes {
96    /// Parses the `MetaNameValue` argument of `keep_attr` attribute. If the argument has
97    /// a correct format `"foo, bar"` then `foo`, `bar` will be included in
98    /// the whitelist of attributes. Else error about parsing will be returned.
99    pub fn parse_arg_value(&mut self, arg: &MetaNameValue) -> Result<(), syn::Error> {
100        if let ast::MetaValue::Lit(syn::Lit::Str(attributes)) = &arg.value {
101            attributes.value().split(',').for_each(|attribute| {
102                self.0.insert(attribute.trim().to_string(), ());
103            });
104            Ok(())
105        } else {
106            Err(format_err_spanned!(
107                arg,
108                "expected a string with attributes separated by `,`",
109            ))
110        }
111    }
112
113    /// Returns the filtered input vector of whitelisted attributes.
114    /// All not whitelisted attributes are removed.
115    pub fn filter_attr(&self, attrs: Vec<syn::Attribute>) -> Vec<syn::Attribute> {
116        attrs
117            .into_iter()
118            .filter(|attr| {
119                if let Some(ident) = attr.path().get_ident() {
120                    self.0.contains_key(&ident.to_string())
121                } else {
122                    false
123                }
124            })
125            .collect()
126    }
127}
128
129/// Return an error to notify about duplicate ink! configuration arguments.
130pub fn duplicate_config_err<F, S>(
131    first: F,
132    second: S,
133    name: &str,
134    ink_attr: &str,
135) -> syn::Error
136where
137    F: Spanned,
138    S: Spanned,
139{
140    format_err!(
141        second.span(),
142        "encountered duplicate ink! {} `{}` configuration argument",
143        ink_attr,
144        name,
145    )
146    .into_combine(format_err!(
147        first.span(),
148        "first `{}` configuration argument here",
149        name
150    ))
151}
152
153/// Finds the salt of a struct, enum or union.
154/// The salt is any generic that has bound `StorageKey`.
155/// In most cases it is the parent storage key or the auto-generated storage key.
156pub fn find_storage_key_salt(input: &syn::DeriveInput) -> Option<syn::TypeParam> {
157    input.generics.params.iter().find_map(|param| {
158        if let syn::GenericParam::Type(type_param) = param
159            && let Some(syn::TypeParamBound::Trait(trait_bound)) =
160                type_param.bounds.first()
161        {
162            let segments = &trait_bound.path.segments;
163            if let Some(last) = segments.last()
164                && last.ident == "StorageKey"
165            {
166                return Some(type_param.clone())
167            }
168        }
169        None
170    })
171}
172
173/// Extracts `cfg` attributes from the given set of attributes
174pub fn extract_cfg_attributes(
175    attrs: &[syn::Attribute],
176    span: Span,
177) -> Vec<proc_macro2::TokenStream> {
178    attrs
179        .iter()
180        .filter(|a| a.path().is_ident(super::CFG_IDENT))
181        .map(|a| quote::quote_spanned!(span=> #a ))
182        .collect()
183}
184
185/// Extracts `cfg` attributes from the given set of attributes
186pub fn extract_cfg_syn_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
187    attrs
188        .iter()
189        .filter(|a| a.path().is_ident(super::CFG_IDENT))
190        .cloned()
191        .collect()
192}
193
194/// Returns `syn::LitStr` value if it's an "identifier-like" string.
195///
196/// # Note
197///
198/// The string is considered to be "identifier-like" if:
199/// - It begins with an alphabetic character, underscore or dollar sign
200/// - It only contains alphanumeric characters, underscores and dollar signs
201pub fn extract_name_override(value: &MetaValue, span: Span) -> syn::Result<syn::LitStr> {
202    if let Some(lit_str) = value.as_lit_string() {
203        let name = lit_str.value();
204        if !name
205            .chars()
206            .next()
207            .map(|c| c.is_alphabetic() || c == '$' || c == '_')
208            .unwrap_or(false)
209        {
210            return Err(format_err_spanned!(
211                lit_str,
212                "`name` attribute argument value must begin with an \
213                alphabetic character, underscore or dollar sign",
214            ));
215        }
216
217        if !name
218            .chars()
219            .all(|c| c.is_alphanumeric() || c == '$' || c == '_')
220        {
221            return Err(format_err_spanned!(
222                lit_str,
223                "`name` attribute argument value can only contain \
224                alphanumeric characters, underscores and dollar signs",
225            ));
226        }
227
228        Ok(lit_str.clone())
229    } else {
230        Err(syn::Error::new(
231            span,
232            "expected a string literal value for `name` \
233            attribute argument",
234        ))
235    }
236}