Unverified Commit 63163f73 authored by Hero Bird's avatar Hero Bird Committed by GitHub
Browse files

Add core/derive crate to add derive macro for some traits (#256)

* [core] add core/derive crate to add derive macro for Flush

* [core/derive] change license from GPL-3.0 to APACHE-2.0

* [core/derive] implement Flush and AllocateUsing derives based on synstructure

* [core] re-export AllocateUsing and Flush derives from within core

* [core/derive] fix AllocateUsing derive impl

* [core/derive] adjust AllocateUsing tests

* [core/derive] apply cargo fmt

* [core/derive] add test::utils and improve AllocateUsing tests

* [core/derive] add explanation docs

* [core/derive] add doc for a hack

* [core/derive] forbid deriving empty enums for Flush

* [core/derive] update compile tests
parent 55377fd6
Pipeline #70164 failed with stages
in 27 minutes and 5 seconds
......@@ -18,6 +18,7 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
ink_abi = { path = "../abi/", default-features = false, features = ["derive"], optional = true }
ink_alloc = { path = "../alloc/", default-features = false }
ink_utils = { path = "../utils/" }
ink_core_derive = { version = "0.1.0", path = "derive", default-features = false, optional = true }
scale = { package = "parity-scale-codec", version = "1.1", default-features = false, features = ["derive", "full"] }
type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
......@@ -27,6 +28,9 @@ cfg-if = "0.1"
[features]
default = ["test-env"]
derive = [
"ink_core_derive",
]
test-env = [
"std",
]
......
[package]
name = "ink_core_derive"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
readme = "../README.md"
[lib]
proc-macro = true
[dependencies]
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
proc-macro2 = "1.0"
synstructure = "0.12"
[dev-dependencies]
ink_core = { path = ".." }
trybuild = "1.0"
[features]
default = ["std"]
std = []
// Copyright 2018-2019 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.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
extern crate proc_macro;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
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! {
extern crate ink_core;
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! {
extern crate ink_core;
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-2019 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.
#[test]
fn compile_tests() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/pass/flush.rs");
t.pass("tests/ui/pass/allocate_using.rs");
t.compile_fail("tests/ui/fail/flush_empty_enum.rs");
t.compile_fail("tests/ui/fail/allocate_using_enum.rs");
}
use ink_core_derive::AllocateUsing;
#[derive(AllocateUsing, Debug, PartialEq, Eq)]
enum Enum {
A,
B,
C,
}
fn main() {}
error: proc-macro derive panicked
--> $DIR/allocate_using_enum.rs:3:10
|
3 | #[derive(AllocateUsing, Debug, PartialEq, Eq)]
| ^^^^^^^^^^^^^
|
= help: message: cannot derive AllocateUsing for enums
use ink_core_derive::Flush;
#[derive(Flush, Debug, PartialEq, Eq)]
enum EmptyEnum {}
fn main() {}
error: proc-macro derive panicked
--> $DIR/flush_empty_enum.rs:3:10
|
3 | #[derive(Flush, Debug, PartialEq, Eq)]
| ^^^^^
|
= help: message: deriving Flush for empty enum is invalid
// Copyright 2018-2019 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 utils;
use utils::*;
use ink_core::storage::Key;
use ink_core_derive::AllocateUsing;
#[derive(Default)]
struct DummyAlloc {
allocated_cells: usize,
allocated_chunks: usize,
}
impl ink_core::storage::alloc::Allocate for DummyAlloc {
fn alloc(&mut self, size: u64) -> Key {
if size == 1 {
self.allocated_cells += 1;
} else {
self.allocated_chunks += 1;
}
Key([0x0; 32])
}
}
#[derive(AllocateUsing, Debug, PartialEq, Eq)]
struct EmptyStruct;
#[derive(AllocateUsing, Debug, PartialEq, Eq)]
struct NewtypeStruct(Cell);
#[derive(AllocateUsing, Debug, PartialEq, Eq)]
struct NamedStruct {
a: Cell,
b: Chunk,
}
#[derive(AllocateUsing, Debug, PartialEq, Eq)]
struct ComplexNamedStruct {
a: Chunk,
b: Value<Cell>,
c: Value<Chunk>,
}
#[derive(AllocateUsing, Debug, PartialEq, Eq)]
struct GenericStruct<T> {
a: Cell,
b: Chunk,
c: Value<T>,
d: Value<Value<T>>,
}
fn test_for<A>(expected_cells_alloc: usize, expected_chunks_alloc: usize)
where
A: ink_core::storage::alloc::AllocateUsing,
{
use ink_core::storage::alloc::AllocateUsing;
let mut alloc = DummyAlloc::default();
unsafe { <A as AllocateUsing>::allocate_using(&mut alloc) };
assert_eq!(
alloc.allocated_cells, expected_cells_alloc,
"number of allocated cells doesn't match expected"
);
assert_eq!(
alloc.allocated_chunks, expected_chunks_alloc,
"number of allocated chunks doesn't match expected"
);
}
fn main() {
test_for::<EmptyStruct>(0, 0);
test_for::<NewtypeStruct>(1, 0);
test_for::<NamedStruct>(1, 1);
test_for::<ComplexNamedStruct>(1, 2);
test_for::<GenericStruct<Cell>>(3, 1);
test_for::<GenericStruct<Chunk>>(1, 3);
}
// Copyright 2018-2019 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 utils;
use ink_core_derive::Flush;
use utils::*;
#[derive(Debug, Default, PartialEq, Eq)]
struct StorageVec<T> {
// We use this for testing if the Flush implementation is somewhat correct.
count_flushed: usize,
// The underlying elements.
//
// Flush is propagated down to them.
elems: Vec<T>,
}
impl<T> ink_core::storage::Flush for StorageVec<T>
where
T: ink_core::storage::Flush,
{
fn flush(&mut self) {
self.count_flushed += 1;
for elem in &mut self.elems {
elem.flush();
}
}
}
#[derive(Flush, Debug, Default, PartialEq, Eq)]
struct UnitStruct;
#[derive(Flush, Debug, Default, PartialEq, Eq)]
struct NewtypeStruct(Cell);
#[derive(Flush, Debug, Default, PartialEq, Eq)]
struct NamedStruct {
a: Cell,
b: Chunk,
}
#[derive(Flush, Debug, Default, PartialEq, Eq)]
struct ComplexNamedStruct {
a: Chunk,
b: Value<Cell>,
c: Value<Chunk>,
}
#[derive(Flush, Debug, Default, PartialEq, Eq)]
struct GenericNamedStruct<T> {
a: Cell,
b: Chunk,
c: Value<T>,
d: StorageVec<Value<T>>,
}
#[derive(Flush, Debug, PartialEq, Eq)]
enum CStyleEnum {
A,
B,
C,
}
impl Default for CStyleEnum {
fn default() -> Self {
Self::A
}
}
#[derive(Flush, Debug, PartialEq, Eq)]
enum TupleStructEnum {
A(Cell),
B(Cell, Chunk),
C(Cell, Chunk, StorageVec<Cell>),
}
impl Default for TupleStructEnum {
fn default() -> Self {
Self::C(Cell::default(), Chunk::default(), StorageVec::<Cell>::default())
}
}
#[derive(Flush, Debug, PartialEq, Eq)]
enum StructEnum {
A {
a: Cell,
},
B {
a: Cell,
b: Chunk,
},
C {
a: Value<Cell>,
b: StorageVec<Cell>,
c: StorageVec<Value<Chunk>>,
},
}
impl Default for StructEnum {
fn default() -> Self {
Self::C {
a: Value::default(),
b: StorageVec::default(),
c: StorageVec::default(),
}
}
}
#[derive(Flush, Debug, PartialEq, Eq)]
enum MixedEnum {
A,
B(Cell, Value<Cell>, StorageVec<Cell>),
C {
a: Chunk,
b: Value<Cell>,
c: StorageVec<Chunk>,
},
}
impl Default for MixedEnum {
fn default() -> Self {
Self::C {
a: Chunk::default(),
b: Value::default(),
c: StorageVec::<Chunk>::default(),
}
}
}
fn test_for<T>(expected: T)
where
T: Default + ink_core::storage::Flush + PartialEq + Eq + core::fmt::Debug,
{
let mut input = T::default();
input.flush();
assert_eq!(input, expected);
}
fn main() {
test_for::<UnitStruct>(Default::default());
test_for::<NewtypeStruct>(NewtypeStruct(Cell { count_flushed: 1 }));
test_for::<NamedStruct>(NamedStruct {
a: Cell { count_flushed: 1 },
b: Chunk { count_flushed: 1 }
});
test_for::<ComplexNamedStruct>(ComplexNamedStruct {
a: Chunk { count_flushed: 1 },
b: Value { value: Cell { count_flushed: 1 } },
c: Value { value: Chunk { count_flushed: 1 } },
});
test_for::<GenericNamedStruct<Cell>>(GenericNamedStruct::<Cell> {
a: Cell { count_flushed: 1 },
b: Chunk { count_flushed: 1 },
c: Value { value: Cell { count_flushed: 1 } },
d: StorageVec { count_flushed: 1, elems: vec![] },
});
test_for::<CStyleEnum>(Default::default());
test_for::<TupleStructEnum>(TupleStructEnum::C(
Cell { count_flushed: 1 },
Chunk { count_flushed: 1 },
StorageVec { count_flushed: 1, elems: vec![] }
));
test_for::<StructEnum>(StructEnum::C {
a: Value { value: Cell { count_flushed: 1 } },
b: StorageVec { count_flushed: 1, elems: vec![] },
c: StorageVec { count_flushed: 1, elems: vec![] },
});
test_for::<MixedEnum>(MixedEnum::C {
a: Chunk { count_flushed: 1 },
b: Value { value: Cell { count_flushed: 1 } },
c: StorageVec { count_flushed: 1, elems: vec![] },
})
}
// Copyright 2018-2019 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.
#[derive(Debug, PartialEq, Eq, Default)]
pub struct Cell {
// We use this for testing if the Flush implementation is somewhat correct.
pub count_flushed: usize,
}
impl ink_core::storage::Flush for Cell {
fn flush(&mut self) {
self.count_flushed += 1;
}
}
impl ink_core::storage::alloc::AllocateUsing for Cell {
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: ink_core::storage::alloc::Allocate,
{
alloc.alloc(1);
Self { count_flushed: 0 }
}
}
#[derive(Debug, PartialEq, Eq, Default)]
pub struct Chunk {
// We use this for testing if the Flush implementation is somewhat correct.
pub count_flushed: usize,
}
impl ink_core::storage::Flush for Chunk {
fn flush(&mut self) {
self.count_flushed += 1;
}
}
impl ink_core::storage::alloc::AllocateUsing for Chunk {
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: ink_core::storage::alloc::Allocate,
{
alloc.alloc(100);
Self { count_flushed: 0 }
}
}
#[derive(Debug, PartialEq, Eq, Default)]
pub struct Value<T> {
pub value: T,
}
impl<T> ink_core::storage::Flush for Value<T>
where
T: ink_core::storage::Flush,
{
fn flush(&mut self) {
self.value.flush()
}
}
impl<T> ink_core::storage::alloc::AllocateUsing for Value<T>
where
T: ink_core::storage::alloc::AllocateUsing,
{
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: ink_core::storage::alloc::Allocate,
{
Self {
value: <T as ink_core::storage::alloc::AllocateUsing>::allocate_using(alloc),
}
}
}
......@@ -31,3 +31,6 @@ pub use self::{
Initialize,
},
};
#[cfg(feature = "derive")]
pub use ink_core_derive::AllocateUsing;
......@@ -40,6 +40,9 @@ pub trait Flush {
fn flush(&mut self);
}
#[cfg(feature = "derive")]
pub use ink_core_derive::Flush;
macro_rules! impl_empty_flush_for {
( $($ty:ty),* ) => {
$(
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment