Skip to content

Commit ff0de16

Browse files
Add packed flag to storage_item attribute and improve related diagnostics (#2722)
* ir: Add `packed` arg to ink! `storage_item` * codegen: Use `packed` arg for ink! `storage_item` * tests: Update ui tests * tests: Update integration tests * storage: Improve diagnostics for missing `Packed` bound * tests: Update ui tests * Update changelog
1 parent ee29802 commit ff0de16

File tree

19 files changed

+341
-461
lines changed

19 files changed

+341
-461
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010
- Implement `bn128` precompiles ‒ [2708](https://github.com/use-ink/ink/pull/2718)
11+
- Add `packed` flag to `storage_item` attribute and improve related diagnostics - [](https://github.com/use-ink/ink/pull/2722)
1112

1213
### Changed
1314
- Refactor contract ref generation and add automatic re-exporting ‒ [#2710](https://github.com/use-ink/ink/pull/2710)

crates/ink/codegen/src/generator/storage_item.rs

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,16 @@ impl GenerateCode for StorageItem<'_> {
5353
Data::Union(union_item) => self.generate_union(union_item),
5454
};
5555

56-
let mut derive = quote! {};
57-
if self.item.config().derive() {
58-
derive = quote! {
56+
let config = self.item.config();
57+
let derive = if config.packed() {
58+
quote! {
59+
#[cfg_attr(feature = "std", derive(
60+
::ink::storage::traits::StorageLayout,
61+
))]
62+
#[ink::scale_derive(Encode, Decode, TypeInfo)]
63+
}
64+
} else if config.derive() {
65+
quote! {
5966
#[cfg_attr(feature = "std", derive(
6067
::ink::storage::traits::StorageLayout,
6168
))]
@@ -65,10 +72,16 @@ impl GenerateCode for StorageItem<'_> {
6572
::ink::storage::traits::StorageKey,
6673
::ink::storage::traits::Storable,
6774
)]
68-
};
69-
}
75+
}
76+
} else {
77+
quote! {}
78+
};
7079

71-
let type_check = self.generate_type_check();
80+
let type_check = if self.item.config().packed() {
81+
quote! {}
82+
} else {
83+
self.generate_type_check()
84+
};
7285

7386
quote! {
7487
#type_check
@@ -88,22 +101,32 @@ impl StorageItem<'_> {
88101
let generics = item.generics();
89102
let salt = item.salt();
90103

91-
let fields = struct_item.fields.iter().enumerate().map(|(i, field)| {
92-
convert_into_storage_field(struct_ident, None, &salt, i, field)
93-
});
104+
let fields = if self.item.config().packed() {
105+
let fields = struct_item.fields.iter();
106+
quote! {
107+
#(#fields),*
108+
}
109+
} else {
110+
let fields = struct_item.fields.iter().enumerate().map(|(i, field)| {
111+
convert_into_storage_field(struct_ident, None, &salt, i, field)
112+
});
113+
quote! {
114+
#(#fields),*
115+
}
116+
};
94117

95118
match struct_item.fields {
96119
Fields::Unnamed(_) => {
97120
quote! {
98121
#vis struct #struct_ident #generics (
99-
#(#fields),*
122+
#fields
100123
);
101124
}
102125
}
103126
_ => {
104127
quote! {
105128
#vis struct #struct_ident #generics {
106-
#(#fields),*
129+
#fields
107130
}
108131
}
109132
}
@@ -126,24 +149,29 @@ impl StorageItem<'_> {
126149
quote! {}
127150
};
128151

129-
let fields: Vec<_> = variant
130-
.fields
131-
.iter()
132-
.enumerate()
133-
.map(|(i, field)| {
152+
let fields = if self.item.config().packed() {
153+
let fields = variant.fields.iter();
154+
quote! {
155+
#(#fields),*
156+
}
157+
} else {
158+
let fields = variant.fields.iter().enumerate().map(|(i, field)| {
134159
convert_into_storage_field(
135160
enum_ident,
136161
Some(variant_ident),
137162
&salt,
138163
i,
139164
field,
140165
)
141-
})
142-
.collect();
166+
});
167+
quote! {
168+
#(#fields),*
169+
}
170+
};
143171

144172
let fields = match variant.fields {
145-
Fields::Named(_) => quote! { { #(#fields),* } },
146-
Fields::Unnamed(_) => quote! { ( #(#fields),* ) },
173+
Fields::Named(_) => quote! { { #fields } },
174+
Fields::Unnamed(_) => quote! { ( #fields ) },
147175
Fields::Unit => quote! {},
148176
};
149177

@@ -167,18 +195,28 @@ impl StorageItem<'_> {
167195
let generics = item.generics();
168196
let salt = item.salt();
169197

170-
let fields = union_item
171-
.fields
172-
.named
173-
.iter()
174-
.enumerate()
175-
.map(|(i, field)| {
176-
convert_into_storage_field(union_ident, None, &salt, i, field)
177-
});
198+
let fields = if self.item.config().packed() {
199+
let fields = union_item.fields.named.iter();
200+
quote! {
201+
#(#fields),*
202+
}
203+
} else {
204+
let fields = union_item
205+
.fields
206+
.named
207+
.iter()
208+
.enumerate()
209+
.map(|(i, field)| {
210+
convert_into_storage_field(union_ident, None, &salt, i, field)
211+
});
212+
quote! {
213+
#(#fields),*
214+
}
215+
};
178216

179217
quote! {
180218
#vis union #union_ident #generics {
181-
#(#fields),*
219+
#fields
182220
}
183221
}
184222
}

crates/ink/ir/src/ir/storage_item/config.rs

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,58 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use syn::spanned::Spanned;
16+
1517
use crate::{
1618
ast,
1719
utils::duplicate_config_err,
1820
};
1921

20-
/// The ink! configuration.
21-
#[derive(Debug, Default, PartialEq, Eq)]
22+
/// The ink! storage item configuration.
23+
#[derive(Debug, PartialEq, Eq)]
2224
pub 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,
2329
/// If set to `true`, all storage related traits are implemented automatically,
2430
/// this is the default value.
2531
/// If set to `false`, implementing all storage traits is disabled. In some cases
2632
/// this can be helpful to override the default implementation of the trait.
2733
derive: bool,
2834
}
2935

36+
impl Default for StorageItemConfig {
37+
fn default() -> Self {
38+
Self {
39+
packed: false,
40+
derive: true,
41+
}
42+
}
43+
}
44+
3045
impl TryFrom<ast::AttributeArgs> for StorageItemConfig {
3146
type Error = syn::Error;
3247

3348
fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
49+
let mut packed: Option<syn::Path> = None;
3450
let mut derive: Option<syn::LitBool> = None;
35-
for arg in args.into_iter() {
36-
if arg.name().is_ident("derive") {
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") {
3767
if let Some(lit_bool) = derive {
3868
return Err(duplicate_config_err(
3969
lit_bool,
@@ -58,15 +88,86 @@ impl TryFrom<ast::AttributeArgs> for StorageItemConfig {
5888
));
5989
}
6090
}
61-
Ok(StorageItemConfig {
62-
derive: derive.map(|lit_bool| lit_bool.value).unwrap_or(true),
63-
})
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 })
64109
}
65110
}
66111

67112
impl StorageItemConfig {
68-
/// Returns the derive configuration argument.
113+
/// Returns the `packed` configuration argument.
114+
pub fn packed(&self) -> bool {
115+
self.packed
116+
}
117+
118+
/// Returns the `derive` configuration argument.
69119
pub fn derive(&self) -> bool {
70120
self.derive
71121
}
72122
}
123+
124+
#[cfg(test)]
125+
mod 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+
}

0 commit comments

Comments
 (0)