Unverified Commit 0c253cb6 authored by Hero Bird's avatar Hero Bird Committed by GitHub
Browse files

Follow-up: Implement storage2 trait derive proc. macros (#399)

* [core/derive] implement SpreadLayout and PackedLayout derives

* [core/derive] no longer is no_std (not useful)

Also simplified its dependencies a bit.

* [core/derive] apply rustfmt

* [core/derive] fix clippy warning
parent e1dcdc80
Pipeline #93540 failed with stages
in 6 minutes and 50 seconds
......@@ -11,18 +11,12 @@ readme = "../README.md"
proc-macro = true
[dependencies]
ink_primitives = { version = "2.1.0", path = "../../primitives", default-features = false }
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
proc-macro2 = "1.0"
synstructure = "0.12"
[dev-dependencies]
ink_primitives = { version = "2.1.0", path = "../../primitives" }
ink_core = { version = "2.1.0", path = ".." }
trybuild = "1.0.24"
[features]
default = ["std"]
std = [
"ink_primitives/std",
]
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
pub(crate) fn allocate_using_derive(mut s: synstructure::Structure) -> TokenStream2 {
// We cannot implement AllocateUsing on enums because we cannot specify
// which variant we are going to use.
if let syn::Data::Enum(ref _enum_data) = s.ast().data {
panic!("cannot derive AllocateUsing for enums")
}
s.bind_with(|_| synstructure::BindStyle::Move);
s.add_bounds(synstructure::AddBounds::Fields);
// The `synstructure` crate treats every input as `enum`.
// So even `struct`s are treated as single-variant enums.
// Some line above we exclude `enum` for deriving this trait so
// all inputs (`struct` only) have exactly one variant which
// is the `struct` itself.
let body = s.variants()[0].construct(|field, _| {
let ty = &field.ty;
quote! {
<#ty as ink_core::storage::alloc::AllocateUsing>::allocate_using(alloc)
}
});
s.gen_impl(quote! {
gen impl ink_core::storage::alloc::AllocateUsing for @Self {
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: ink_core::storage::alloc::Allocate,
{
#body
}
}
})
}
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
pub(crate) fn flush_derive(mut s: synstructure::Structure) -> TokenStream2 {
if s.variants().is_empty() {
panic!("deriving Flush for empty enum is invalid")
}
s.bind_with(|_| synstructure::BindStyle::Move);
s.add_bounds(synstructure::AddBounds::Fields);
// Upon seeing the first variant we set this to true.
// This is needed so that we do not have a `match self`
// for empty enums which apparently causes errors.
// If there is a better solution to tackle this please
// update this.
let mut requires_match = false;
let body = s.each(|bi| {
requires_match = true;
quote! {
ink_core::storage::Flush::flush(#bi)
}
});
let body = if requires_match {
quote! {
match self {
#body
}
}
} else {
quote! {}
};
s.gen_impl(quote! {
gen impl ink_core::storage::Flush for @Self {
fn flush(&mut self) {
#body
}
}
})
}
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
......@@ -12,80 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
extern crate proc_macro;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
mod allocate_using;
mod flush;
mod packed_layout;
mod spread_layout;
#[cfg(test)]
mod tests;
use self::{
allocate_using::allocate_using_derive,
flush::flush_derive,
packed_layout::packed_layout_derive,
spread_layout::spread_layout_derive,
};
synstructure::decl_derive!([Flush] => flush_derive);
synstructure::decl_derive!([AllocateUsing] => allocate_using_derive);
pub(crate) fn flush_derive(mut s: synstructure::Structure) -> TokenStream2 {
if s.variants().is_empty() {
panic!("deriving Flush for empty enum is invalid")
}
s.bind_with(|_| synstructure::BindStyle::Move);
s.add_bounds(synstructure::AddBounds::Fields);
// Upon seeing the first variant we set this to true.
// This is needed so that we do not have a `match self`
// for empty enums which apparently causes errors.
// If there is a better solution to tackle this please
// update this.
let mut requires_match = false;
let body = s.each(|bi| {
requires_match = true;
quote! {
ink_core::storage::Flush::flush(#bi)
}
});
let body = if requires_match {
quote! {
match self {
#body
}
}
} else {
quote! {}
};
s.gen_impl(quote! {
gen impl ink_core::storage::Flush for @Self {
fn flush(&mut self) {
#body
}
}
})
}
pub(crate) fn allocate_using_derive(mut s: synstructure::Structure) -> TokenStream2 {
// We cannot implement AllocateUsing on enums because we cannot specify
// which variant we are going to use.
if let syn::Data::Enum(ref _enum_data) = s.ast().data {
panic!("cannot derive AllocateUsing for enums")
}
s.bind_with(|_| synstructure::BindStyle::Move);
s.add_bounds(synstructure::AddBounds::Fields);
// The `synstructure` crate treats every input as `enum`.
// So even `struct`s are treated as single-variant enums.
// Some line above we exclude `enum` for deriving this trait so
// all inputs (`struct` only) have exactly one variant which
// is the `struct` itself.
let body = s.variants()[0].construct(|field, _| {
let ty = &field.ty;
quote! {
<#ty as ink_core::storage::alloc::AllocateUsing>::allocate_using(alloc)
}
});
s.gen_impl(quote! {
gen impl ink_core::storage::alloc::AllocateUsing for @Self {
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: ink_core::storage::alloc::Allocate,
{
#body
}
}
})
}
synstructure::decl_derive!([SpreadLayout] => spread_layout_derive);
synstructure::decl_derive!([PackedLayout] => packed_layout_derive);
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
/// Derives `ink_core`'s `PackedLayout` trait for the given `struct` or `enum`.
pub fn packed_layout_derive(mut s: synstructure::Structure) -> TokenStream2 {
s.bind_with(|_| synstructure::BindStyle::Move);
s.add_bounds(synstructure::AddBounds::Generics);
let pull_body = s.each(|binding| {
quote! { ::ink_core::storage2::traits::PackedLayout::pull_packed(#binding, __key); }
});
let push_body = s.each(|binding| {
quote! { ::ink_core::storage2::traits::PackedLayout::push_packed(#binding, __key); }
});
let clear_body = s.each(|binding| {
quote! { ::ink_core::storage2::traits::PackedLayout::clear_packed(#binding, __key); }
});
s.gen_impl(quote! {
gen impl ::ink_core::storage2::traits::PackedLayout for @Self {
fn pull_packed(&mut self, __key: &::ink_primitives::Key) {
match self { #pull_body }
}
fn push_packed(&self, __key: &::ink_primitives::Key) {
match self { #push_body }
}
fn clear_packed(&self, __key: &::ink_primitives::Key) {
match self { #clear_body }
}
}
})
}
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
/// Generates the tokens to compute the maximum of the numbers given via
/// their token streams at compilation time.
///
/// # Note
///
/// Since Rust currently does not allow conditionals in const contexts
/// we use the array indexing trick to compute the maximum element:
///
/// ```no_compile
/// max(a, b) = [a, b][(a < b) as usize]
/// ```
fn max_n(args: &[TokenStream2]) -> TokenStream2 {
match args.split_first() {
Some((head, rest)) => {
let rest = max_n(rest);
quote! {
[#head, #rest][(#head < #rest) as usize]
}
}
None => quote! { 0u64 },
}
}
/// Generates the tokens for the `SpreadLayout` footprint of some type.
fn footprint(s: &synstructure::Structure) -> TokenStream2 {
let variant_footprints = s
.variants()
.iter()
.map(|variant| {
variant
.ast()
.fields
.iter()
.map(|field| &field.ty)
.map(|ty| quote! { <#ty as ::ink_core::storage2::traits::SpreadLayout>::FOOTPRINT })
.fold(quote! { 0u64 }, |lhs, rhs| {
quote! { (#lhs + #rhs) }
})
})
.collect::<Vec<_>>();
max_n(&variant_footprints[..])
}
/// Generates the tokens for the `SpreadLayout` `REQUIRES_DEEP_CLEAN_UP` constant for the given structure.
fn requires_deep_clean_up(s: &synstructure::Structure) -> TokenStream2 {
s.variants()
.iter()
.map(|variant| {
variant
.ast()
.fields
.iter()
.map(|field| &field.ty)
.map(|ty| quote! { <#ty as ::ink_core::storage2::traits::SpreadLayout>::REQUIRES_DEEP_CLEAN_UP })
.fold(quote! { false }, |lhs, rhs| {
quote! { (#lhs || #rhs) }
})
})
.fold(quote! { false }, |lhs, rhs| {
quote! { (#lhs || #rhs) }
})
}
/// `SpreadLayout` derive implementation for `struct` types.
fn spread_layout_struct_derive(s: &synstructure::Structure) -> TokenStream2 {
assert!(s.variants().len() == 1, "can only operate on structs");
let footprint_body = footprint(s);
let requires_deep_clean_up_body = requires_deep_clean_up(s);
let variant: &synstructure::VariantInfo = &s.variants()[0];
let pull_body = variant.construct(|field, _index| {
let ty = &field.ty;
quote! {
<#ty as ::ink_core::storage2::traits::SpreadLayout>::pull_spread(__key_ptr)
}
});
let push_body = variant.each(|binding| {
quote! {
::ink_core::storage2::traits::SpreadLayout::push_spread(#binding, __key_ptr);
}
});
let clear_body = s.each(|field| {
quote! {
::ink_core::storage2::traits::SpreadLayout::clear_spread(#field, __key_ptr);
}
});
s.gen_impl(quote! {
gen impl ::ink_core::storage2::traits::SpreadLayout for @Self {
#[allow(unused_comparisons)]
const FOOTPRINT: u64 = #footprint_body;
const REQUIRES_DEEP_CLEAN_UP: bool = #requires_deep_clean_up_body;
fn pull_spread(__key_ptr: &mut ::ink_core::storage2::traits::KeyPtr) -> Self {
#pull_body
}
fn push_spread(&self, __key_ptr: &mut ::ink_core::storage2::traits::KeyPtr) {
match self { #push_body }
}
fn clear_spread(&self, __key_ptr: &mut ::ink_core::storage2::traits::KeyPtr) {
match self { #clear_body }
}
}
})
}
/// `SpreadLayout` derive implementation for `enum` types.
fn spread_layout_enum_derive(s: &synstructure::Structure) -> TokenStream2 {
assert!(s.variants().len() >= 2, "can only operate on enums");
let footprint_body = footprint(s);
let requires_deep_clean_up_body = requires_deep_clean_up(s);
let pull_body = s
.variants()
.iter()
.map(|variant| {
variant.construct(|field, _index| {
let ty = &field.ty;
quote! {
<#ty as ::ink_core::storage2::traits::SpreadLayout>::pull_spread(__key_ptr)
}
})
})
.enumerate()
.fold(quote! {}, |acc, (index, variant)| {
let index = index as u8;
quote! {
#acc
#index => #variant,
}
});
let push_body = s.variants().iter().enumerate().map(|(index, variant)| {
let pat = variant.pat();
let index = index as u8;
let fields = variant.bindings().iter().map(|field| {
quote! {
::ink_core::storage2::traits::SpreadLayout::push_spread(#field, __key_ptr);
}
});
quote! {
#pat => {
{ <u8 as ::ink_core::storage2::traits::SpreadLayout>::push_spread(&#index, __key_ptr); }
#(
{ #fields }
)*
}
}
});
let clear_body = s.each(|field| {
quote! {
::ink_core::storage2::traits::SpreadLayout::clear_spread(#field, __key_ptr);
}
});
s.gen_impl(quote! {
gen impl ::ink_core::storage2::traits::SpreadLayout for @Self {
#[allow(unused_comparisons)]
const FOOTPRINT: u64 = 1 + #footprint_body;
const REQUIRES_DEEP_CLEAN_UP: bool = #requires_deep_clean_up_body;
fn pull_spread(__key_ptr: &mut ::ink_core::storage2::traits::KeyPtr) -> Self {
match <u8 as ::ink_core::storage2::traits::SpreadLayout>::pull_spread(__key_ptr) {
#pull_body
_ => unreachable!("encountered invalid enum discriminant"),
}
}
fn push_spread(&self, __key_ptr: &mut ::ink_core::storage2::traits::KeyPtr) {
match self {
#(
#push_body
)*
}
}
fn clear_spread(&self, __key_ptr: &mut ::ink_core::storage2::traits::KeyPtr) {
match self {
#clear_body
}
}
}
})
}
/// Derives `ink_core`'s `SpreadLayout` trait for the given `struct` or `enum`.
pub fn spread_layout_derive(mut s: synstructure::Structure) -> TokenStream2 {
s.bind_with(|_| synstructure::BindStyle::Move);
s.add_bounds(synstructure::AddBounds::Generics);
let is_enum = s.variants().len() >= 2;
if is_enum {
spread_layout_enum_derive(&s)
} else if s.variants().len() == 1 {
spread_layout_struct_derive(&s)
} else {
panic!("empty enums are not supported");
}
}
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod packed_layout;
mod spread_layout;
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::packed_layout_derive;
#[test]
fn unit_struct_works() {
synstructure::test_derive! {
packed_layout_derive {
struct UnitStruct;
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_ink_core_storage2_traits_PackedLayout_FOR_UnitStruct: () = {
impl ::ink_core::storage2::traits::PackedLayout for UnitStruct {
fn pull_packed(&mut self, __key: &::ink_primitives::Key) {
match self {
UnitStruct => {}
}
}
fn push_packed(&self, __key: &::ink_primitives::Key) {
match self {
UnitStruct => {}
}
}
fn clear_packed(&self, __key: &::ink_primitives::Key) {
match self {
UnitStruct => {}
}
}
}
};
}
no_build
}
}
#[test]
fn struct_works() {
synstructure::test_derive! {
packed_layout_derive {
struct NamedFields {
a: i32,
b: [u8; 32],
d: Box<i32>,
}
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_ink_core_storage2_traits_PackedLayout_FOR_NamedFields: () = {
impl ::ink_core::storage2::traits::PackedLayout for NamedFields {
fn pull_packed(&mut self, __key: &::ink_primitives::Key) {
match self {
NamedFields {
a: __binding_0,
b: __binding_1,
d: __binding_2,
} => {
{
::ink_core::storage2::traits::PackedLayout::pull_packed(__binding_0, __key);
}
{
::ink_core::storage2::traits::PackedLayout::pull_packed(__binding_1, __key);
}
{
::ink_core::storage2::traits::PackedLayout::pull_packed(__binding_2, __key);
}
}
}
}
fn push_packed(&self, __key: &::ink_primitives::Key) {
match self {
NamedFields {
a: __binding_0,
b: __binding_1,
d: __binding_2,
} => {
{
::ink_core::storage2::traits::PackedLayout::push_packed(__binding_0, __key);
}
{
::ink_core::storage2::traits::PackedLayout::push_packed(__binding_1, __key);
}
{
::ink_core::storage2::traits::PackedLayout::push_packed(__binding_2, __key);
}
}
}
}
fn clear_packed(&self, __key: &::ink_primitives::Key) {
match self {
NamedFields {
a: __binding_0,