ink_ir/ir/storage_item/
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 syn::spanned::Spanned;
16
17use crate::{
18    ast,
19    utils::duplicate_config_err,
20};
21
22/// The ink! storage item configuration.
23#[derive(Debug, PartialEq, Eq)]
24pub struct StorageItemConfig {
25    /// If set to `true`, the derived storage item will use a "packed" layout.
26    /// If set to `false`, the derived storage item will use a "non-packed" layout,
27    /// this is the default value.
28    packed: bool,
29    /// If set to `true`, all storage related traits are implemented automatically,
30    /// this is the default value.
31    /// If set to `false`, implementing all storage traits is disabled. In some cases
32    /// this can be helpful to override the default implementation of the trait.
33    derive: bool,
34}
35
36impl Default for StorageItemConfig {
37    fn default() -> Self {
38        Self {
39            packed: false,
40            derive: true,
41        }
42    }
43}
44
45impl TryFrom<ast::AttributeArgs> for StorageItemConfig {
46    type Error = syn::Error;
47
48    fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
49        let mut packed: Option<syn::Path> = None;
50        let mut derive: Option<syn::LitBool> = None;
51        let args_span = args.span();
52        for arg in args {
53            if arg.name().is_ident("packed") {
54                if let Some(path) = packed {
55                    return Err(duplicate_config_err(path, arg, "packed", "storage item"));
56                }
57                if let ast::Meta::Path(path) = arg {
58                    packed = Some(path)
59                } else {
60                    return Err(format_err_spanned!(
61                        arg,
62                        "encountered an unexpected value for `packed` ink! storage item configuration argument. \
63                        Did you mean `#[ink::storage_item(packed)]` ?",
64                    ));
65                }
66            } else if arg.name().is_ident("derive") {
67                if let Some(lit_bool) = derive {
68                    return Err(duplicate_config_err(
69                        lit_bool,
70                        arg,
71                        "derive",
72                        "storage item",
73                    ));
74                }
75                if let Some(lit_bool) = arg.value().and_then(ast::MetaValue::as_lit_bool)
76                {
77                    derive = Some(lit_bool.clone())
78                } else {
79                    return Err(format_err_spanned!(
80                        arg,
81                        "expected a bool literal value for `derive` ink! storage item configuration argument",
82                    ));
83                }
84            } else {
85                return Err(format_err_spanned!(
86                    arg,
87                    "encountered unknown or unsupported ink! storage item configuration argument",
88                ));
89            }
90        }
91
92        // Sanitize user-provided configuration.
93        let (packed, derive) = match (packed, derive.map(|lit_bool| lit_bool.value)) {
94            // `packed` (i.e. `packed=true`) and `derive=false` conflict.
95            // Note: There's really no reasonable use case for this combination.
96            (Some(_), Some(false)) => {
97                return Err(format_err!(
98                    args_span,
99                    "cannot use `derive = false` with `packed` flag",
100                ))
101            }
102            // Otherwise, accept the user provided configuration,
103            // while defaulting to "non-packed" layout (resolved as `packed=false`) and
104            // `derive=true`.
105            (packed, derive) => (packed.is_some(), derive.unwrap_or(true)),
106        };
107
108        Ok(StorageItemConfig { packed, derive })
109    }
110}
111
112impl StorageItemConfig {
113    /// Returns the `packed` configuration argument.
114    pub fn packed(&self) -> bool {
115        self.packed
116    }
117
118    /// Returns the `derive` configuration argument.
119    pub fn derive(&self) -> bool {
120        self.derive
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use quote::quote;
128
129    #[test]
130    fn valid_args_works() {
131        for (config, packed, derive) in [
132            // Defaults are "non-packed" layout (resolved as `packed=false`) with
133            // `derive=true`.
134            (quote!(), false, true),
135            // `packed` (resolved as `packed=true`) works only with `derive=true`.
136            (quote! { packed }, true, true),
137            (quote! { packed, derive = true }, true, true),
138            // "non-packed" layout (resolved as `packed=false`) works with any `derive`
139            // arg.
140            (quote! { derive = true }, false, true),
141            (quote! { derive = false }, false, false),
142        ] {
143            let parsed_config = syn::parse2::<crate::ast::AttributeArgs>(config).unwrap();
144            let result = StorageItemConfig::try_from(parsed_config);
145            assert!(result.is_ok());
146            let storage_item_config = result.unwrap();
147            assert_eq!(storage_item_config.packed(), packed);
148            assert_eq!(storage_item_config.derive(), derive);
149        }
150    }
151
152    #[test]
153    #[should_panic = "cannot use `derive = false` with `packed` flag"]
154    fn conflicting_args_fails() {
155        let config = quote! {
156            // `packed` and `derive = false` conflict.
157            packed, derive = false
158        };
159        let parsed_config = syn::parse2::<crate::ast::AttributeArgs>(config).unwrap();
160        StorageItemConfig::try_from(parsed_config).unwrap();
161    }
162
163    #[test]
164    #[should_panic = "encountered an unexpected value for `packed` ink! storage item configuration argument. Did you mean `#[ink::storage_item(packed)]` ?"]
165    fn invalid_packed_value_fails() {
166        let config = quote! {
167            // `packed` arg doesn't accept a value.
168            packed = true
169        };
170        let parsed_config = syn::parse2::<crate::ast::AttributeArgs>(config).unwrap();
171        StorageItemConfig::try_from(parsed_config).unwrap();
172    }
173}