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