ink_ir/ir/
config.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 crate::{
16    ast,
17    utils::{
18        duplicate_config_err,
19        WhitelistedAttributes,
20    },
21};
22
23/// The ink! configuration.
24#[derive(Debug, Default, PartialEq, Eq)]
25pub struct Config {
26    /// The environmental types definition.
27    ///
28    /// This must be a type that implements `ink_env::Environment` and can
29    /// be used to change the underlying environmental types of an ink! smart
30    /// contract.
31    env: Option<Environment>,
32    /// The set of attributes that can be passed to call builder in the codegen.
33    whitelisted_attributes: WhitelistedAttributes,
34}
35
36impl TryFrom<ast::AttributeArgs> for Config {
37    type Error = syn::Error;
38
39    fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
40        let mut env: Option<(Environment, ast::MetaNameValue)> = None;
41        let mut whitelisted_attributes = WhitelistedAttributes::default();
42
43        for arg in args.into_iter() {
44            if arg.name().is_ident("env") {
45                if let Some((_, ast)) = env {
46                    return Err(duplicate_config_err(ast, arg, "env", "contract"));
47                }
48                let env_info = arg
49                    .name_value()
50                    .zip(arg.value().and_then(ast::MetaValue::as_path));
51                if let Some((name_value, path)) = env_info {
52                    env = Some((Environment { path: path.clone() }, name_value.clone()))
53                } else {
54                    return Err(format_err_spanned!(
55                        arg,
56                        "expected a path value for `env` ink! configuration argument",
57                    ));
58                }
59            } else if arg.name().is_ident("keep_attr") {
60                if let Some(name_value) = arg.name_value() {
61                    whitelisted_attributes.parse_arg_value(name_value)?;
62                } else {
63                    return Err(format_err_spanned!(
64                        arg,
65                        "expected a string literal value for `keep_attr` ink! configuration argument",
66                    ));
67                }
68            } else {
69                return Err(format_err_spanned!(
70                    arg,
71                    "encountered unknown or unsupported ink! configuration argument",
72                ));
73            }
74        }
75        Ok(Config {
76            env: env.map(|(value, _)| value),
77            whitelisted_attributes,
78        })
79    }
80}
81
82impl Config {
83    /// Returns the environmental types definition if specified.
84    /// Otherwise returns the default environmental types definition provided
85    /// by ink!.
86    pub fn env(&self) -> syn::Path {
87        self.env
88            .as_ref()
89            .map(|env| &env.path)
90            .cloned()
91            .unwrap_or(Environment::default().path)
92    }
93
94    /// Return set of attributes that can be passed to call builder in the codegen.
95    pub fn whitelisted_attributes(&self) -> &WhitelistedAttributes {
96        &self.whitelisted_attributes
97    }
98}
99
100/// The environmental types definition.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct Environment {
103    /// The underlying Rust type.
104    pub path: syn::Path,
105}
106
107impl Default for Environment {
108    fn default() -> Self {
109        Self {
110            path: syn::parse_quote! { ::ink::env::DefaultEnvironment },
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    /// Asserts that the given input configuration attribute argument are converted
120    /// into the expected ink! configuration or yields the expected error message.
121    fn assert_try_from(
122        input: ast::AttributeArgs,
123        expected: Result<Config, &'static str>,
124    ) {
125        assert_eq!(
126            <Config as TryFrom<ast::AttributeArgs>>::try_from(input)
127                .map_err(|err| err.to_string()),
128            expected.map_err(ToString::to_string),
129        );
130    }
131
132    #[test]
133    fn empty_config_works() {
134        assert_try_from(syn::parse_quote! {}, Ok(Config::default()))
135    }
136
137    #[test]
138    fn env_works() {
139        assert_try_from(
140            syn::parse_quote! {
141                env = ::my::env::Types
142            },
143            Ok(Config {
144                env: Some(Environment {
145                    path: syn::parse_quote! { ::my::env::Types },
146                }),
147                whitelisted_attributes: Default::default(),
148            }),
149        )
150    }
151
152    #[test]
153    fn env_invalid_value_fails() {
154        assert_try_from(
155            syn::parse_quote! { env = "invalid" },
156            Err("expected a path value for `env` ink! configuration argument"),
157        );
158    }
159
160    #[test]
161    fn env_missing_value_fails() {
162        assert_try_from(
163            syn::parse_quote! { env },
164            Err("expected a path value for `env` ink! configuration argument"),
165        );
166    }
167
168    #[test]
169    fn unknown_arg_fails() {
170        assert_try_from(
171            syn::parse_quote! { unknown = argument },
172            Err("encountered unknown or unsupported ink! configuration argument"),
173        );
174    }
175
176    #[test]
177    fn duplicate_args_fails() {
178        assert_try_from(
179            syn::parse_quote! {
180                env = ::my::env::Types,
181                env = ::my::other::env::Types,
182            },
183            Err("encountered duplicate ink! contract `env` configuration argument"),
184        );
185    }
186
187    #[test]
188    fn keep_attr_works() {
189        let mut attrs = WhitelistedAttributes::default();
190        attrs.0.insert("foo".to_string(), ());
191        attrs.0.insert("bar".to_string(), ());
192        assert_try_from(
193            syn::parse_quote! {
194                keep_attr = "foo, bar"
195            },
196            Ok(Config {
197                env: None,
198                whitelisted_attributes: attrs,
199            }),
200        )
201    }
202
203    #[test]
204    fn keep_attr_invalid_value_fails() {
205        assert_try_from(
206            syn::parse_quote! { keep_attr = 1u16 },
207            Err("expected a string with attributes separated by `,`"),
208        );
209    }
210
211    #[test]
212    fn keep_attr_missing_value_fails() {
213        assert_try_from(
214            syn::parse_quote! { keep_attr },
215            Err("expected a string literal value for `keep_attr` ink! configuration argument"),
216        );
217    }
218}