Skip to content

Commit

Permalink
Implement a proc-macro derive to specify field projections safely (#5)
Browse files Browse the repository at this point in the history
* basic implementation of derive

as per specification of #2

* updated derive macro to work with new changes to core
adapted core tests to use derive macro

* added docs for using the derive

* Merge branch 'master' into derive (#6)

* added a pin to reference projection

* updated docs

* updated to be const friendly and fixed privacy bug

* added some inline attributes

* added support for tuple structs

* updated test

* added inline attributes for `project_set`
  • Loading branch information
RustyYato committed Sep 29, 2019
1 parent 95c3495 commit 778d0d0
Show file tree
Hide file tree
Showing 11 changed files with 487 additions and 80 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
@@ -1,5 +1,6 @@
[workspace]

members = [
'core'
'core',
'derive'
]
1 change: 1 addition & 0 deletions core/Cargo.toml
Expand Up @@ -7,3 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
gfp-derive = { path = '../derive' }
2 changes: 1 addition & 1 deletion core/src/chain.rs
Expand Up @@ -9,7 +9,7 @@ pub struct Chain<A, B> {

impl<A, B> Chain<A, B> {
#[inline]
pub(crate) fn new(a: A, b: B) -> Self {
pub const fn new(a: A, b: B) -> Self {
Self { a, b }
}
}
Expand Down
64 changes: 64 additions & 0 deletions core/src/lib.rs
Expand Up @@ -18,6 +18,13 @@ mod set;
pub use self::pin::*;
pub use self::chain::*;
pub use self::set::{FieldSet, tuple::*};
pub use gfp_derive::Field;

#[doc(hidden)]
pub mod derive {
pub use core::marker::PhantomData;
pub use core::iter::{Once, once};
}

/// Projects a type to the given field
pub trait ProjectTo<F: Field> {
Expand All @@ -38,6 +45,19 @@ pub trait ProjectToSet<F: FieldSet> {
}

/// Represents a field of some `Parent` type
/// You can derive this trait for all fields of `Parent` by using
///
/// ```rust
/// # mod __ {
/// use gfp_core::Field;
///
/// #[derive(Field)]
/// struct Foo {
/// bar: u32,
/// val: String
/// }
/// # }
/// ```
///
/// # Safety
///
Expand Down Expand Up @@ -101,6 +121,50 @@ pub trait ProjectToSet<F: FieldSet> {
/// }
/// ```
///
/// But it is better to just derive `Field` on the types that you need to, and then use the [`chain`](trait.Fields.html#method.chain)
/// combinator to project to the fields of fields
///
/// ```rust
/// # mod main {
/// use gfp_core::Field;
///
/// #[derive(Field)]
/// struct Foo {
/// bar: Bar
/// }
///
/// #[derive(Field)]
/// struct Bar {
/// tap: Tap
/// }
///
/// #[derive(Field)]
/// struct Tap {
/// val: u32
/// }
///
/// fn main() {
/// let foo_to_val = Foo::fields().bar.chain(
/// Bar::fields().tap
/// ).chain(
/// Tap::fields().val
/// );
///
/// // or if you are going to use that projection a lot
///
/// use gfp_core::Chain;
///
/// const FOO_TO_VAL: Chain<Chain<Foo_fields::bar::<Foo>, Bar_fields::tap::<Bar>>, Tap_fields::val::<Tap>> = Chain::new(
/// Chain::new(
/// Foo::FIELDS.bar,
/// Bar::FIELDS.tap,
/// ),
/// Tap::FIELDS.val,
/// );
/// }
/// # }
/// ```
///
pub unsafe trait Field {
/// The type that the field comes from
type Parent: ?Sized;
Expand Down
9 changes: 9 additions & 0 deletions core/src/project_set.rs
Expand Up @@ -15,6 +15,7 @@ where F::Parent: 'a,
F::TypeSet: TupleMap<PtrToRef<'a>> {
type Projection = TMap<F::TypeSet, PtrToRef<'a>>;

#[inline]
fn project_set_to(self, field: F) -> Self::Projection {
unsafe {
let type_set = field.project_raw(self);
Expand All @@ -32,6 +33,7 @@ impl<S, I: Field> TypeFunction<I> for FindOverlap<S>
where S: Copy + TupleAny<FindOverlapInner<I>> {
type Output = bool;

#[inline]
fn call(&mut self, input: I) -> bool {
self.counter += 1;
self.set.tup_any(FindOverlapInner {
Expand All @@ -51,6 +53,7 @@ pub struct FindOverlapInner<I> {
impl<I: Field, J: Field> TypeFunction<J> for FindOverlapInner<I> {
type Output = bool;

#[inline]
fn call(&mut self, input: J) -> bool {
self.counter += 1;

Expand All @@ -70,6 +73,7 @@ where F::Parent: 'a,
F: Copy + TupleAny<FindOverlap<F>> {
type Projection = TMap<F::TypeSetMut, PtrToRefMut<'a>>;

#[inline]
fn project_set_to(self, field: F) -> Self::Projection {
unsafe {
if field.tup_any(FindOverlap {
Expand All @@ -93,12 +97,14 @@ pub struct CheckMake;
impl<F: Field> TypeFunction<PPF<F>> for CheckMake {
type Output = MakePin;

#[inline]
fn call(&mut self, _: PPF<F>) -> Self::Output { MakePin }
}

impl<F: Field> TypeFunction<PTR<F>> for CheckMake {
type Output = MakeRef;

#[inline]
fn call(&mut self, _: PTR<F>) -> Self::Output { MakeRef }
}

Expand All @@ -107,6 +113,7 @@ pub struct PinCombine;
impl<T: Deref> TypeFunction<(MakePin, T)> for PinCombine {
type Output = Pin<T>;

#[inline]
fn call(&mut self, (MakePin, value): (MakePin, T)) -> Self::Output {
unsafe { Pin::new_unchecked(value) }
}
Expand All @@ -115,6 +122,7 @@ impl<T: Deref> TypeFunction<(MakePin, T)> for PinCombine {
impl<T> TypeFunction<(MakeRef, T)> for PinCombine {
type Output = T;

#[inline]
fn call(&mut self, (MakeRef, value): (MakeRef, T)) -> Self::Output {
value
}
Expand All @@ -127,6 +135,7 @@ where
{
type Projection = TZip<TMap<F, CheckMake>, P::Projection, PinCombine>;

#[inline]
fn project_set_to(self, field: F) -> Self::Projection {
let check_make = field.tup_map(CheckMake);
unsafe {
Expand Down
4 changes: 4 additions & 0 deletions core/src/set/func.rs
Expand Up @@ -4,6 +4,7 @@ use core::marker::PhantomData;
pub struct PtrToRef<'a>(PhantomData<&'a ()>);

impl<'a> PtrToRef<'a> {
#[inline]
pub(crate) unsafe fn new() -> Self {
Self(PhantomData)
}
Expand All @@ -12,6 +13,7 @@ impl<'a> PtrToRef<'a> {
impl<'a, T: ?Sized + 'a> TypeFunction<*const T> for PtrToRef<'a> {
type Output = &'a T;

#[inline]
fn call(&mut self, input: *const T) -> Self::Output {
unsafe { &*input }
}
Expand All @@ -20,6 +22,7 @@ impl<'a, T: ?Sized + 'a> TypeFunction<*const T> for PtrToRef<'a> {
pub struct PtrToRefMut<'a>(PhantomData<&'a ()>);

impl<'a> PtrToRefMut<'a> {
#[inline]
pub(crate) unsafe fn new() -> Self {
Self(PhantomData)
}
Expand All @@ -28,6 +31,7 @@ impl<'a> PtrToRefMut<'a> {
impl<'a, T: ?Sized + 'a> TypeFunction<*mut T> for PtrToRefMut<'a> {
type Output = &'a mut T;

#[inline]
fn call(&mut self, input: *mut T) -> Self::Output {
unsafe { &mut *input }
}
Expand Down
86 changes: 26 additions & 60 deletions core/tests/test_core.rs
Expand Up @@ -2,37 +2,26 @@

use gfp_core::*;

#[derive(Default)]
#[derive(Default, Field)]
struct Foo {
x: u8,
y: Bar,
z: u128,
}

#[derive(Default)]
#[derive(Default, Field)]
struct Bar {
a: u16,
b: u32,
c: Quaz,
}

#[derive(Default)]
#[derive(Default, Field)]
struct Quaz {
q: (u16, u32),
r: u32,
}

field!(Foo_x(Foo => u8), x, Foo::default());
field!(Foo_y(Foo => Bar), y, Foo::default());
field!(Foo_z(Foo => u128), z, Foo::default());

field!(Bar_a(Bar => u16), a, Bar::default());
field!(Bar_b(Bar => u32), b, Bar::default());
field!(Bar_c(Bar => Quaz), c, Bar::default());

field!(Quaz_q(Quaz => (u16, u32)), q, Quaz::default());
field!(Quaz_r(Quaz => u32), r, Quaz::default());

#[test]
#[allow(non_camel_case_types)]
fn simple_test() {
Expand All @@ -44,56 +33,32 @@ fn simple_test() {

let foo_pin = Pin::new(&foo);

let x = Foo::fields().x;

unsafe {
assert_eq!(*foo_pin.project_to(PinProjectableField::new_unchecked(Foo_x)), 3);
assert_eq!(*foo_pin.project_to(PinProjectableField::new_unchecked(x)), 3);
}
}

#[derive(Field)]
struct MyType<T> {
x: u8,
y: u8,
z: T
}

#[test]
pub fn generic_test() {
use core::marker::PhantomData;

#[repr(C)]
struct MyType<T> {
x: u8,
y: u8,
z: T
}

#[allow(non_camel_case_types)]
struct MyType_z<T>(PhantomData<fn(T) -> T>);

unsafe impl<T> Field for MyType_z<T> {
type Parent = MyType<T>;
type Type = T;
type Name = core::iter::Once<&'static str>;

fn name(&self) -> Self::Name {
core::iter::once("z")
}

unsafe fn project_raw(&self, ptr: *const Self::Parent) -> *const Self::Type {
&(*ptr).z
}

unsafe fn project_raw_mut(&self, ptr: *mut Self::Parent) -> *mut Self::Type {
&mut (*ptr).z
}
}

impl<T> MyType_z<T> {
pub fn new() -> Self {
Self(PhantomData)
}

pub fn pin() -> PinProjectableField<Self> {
impl<T> MyType_fields::z<MyType<T>> {
pub fn pin(self) -> PinProjectableField<Self> {
unsafe {
PinProjectableField::new_unchecked(Self::new())
PinProjectableField::new_unchecked(self)
}
}
}

assert_eq!(core::mem::size_of_val(&MyType_z::<u32>::pin()), 0);
let z = MyType::fields().z;
assert_eq!(core::mem::size_of_val(&z.pin()), 0);

use core::pin::Pin;

Expand All @@ -105,21 +70,18 @@ pub fn generic_test() {

let my_type_pin = Pin::new(&my_type);

unsafe {
assert_eq!(*my_type_pin.project_to(PinProjectableField::new_unchecked(MyType_z::new())), 3);
}
assert_eq!(*my_type_pin.project_to(z.pin()), 3);

let my_type = MyType {
x: 0,
y: 1,
z: 3u32
};

let z = MyType::fields().z;
let my_type_pin = Pin::new(&my_type);

unsafe {
assert_eq!(*my_type_pin.project_to(PinProjectableField::new_unchecked(MyType_z::new())), 3);
}
assert_eq!(*my_type_pin.project_to(z.pin()), 3);
}

#[test]
Expand All @@ -138,8 +100,12 @@ fn test_chain() {
z: 6
};

let foo = Foo::fields();
let bar = Bar::fields();
let quaz = Quaz::fields();

assert_eq!(
*my_type.project_to(&Foo_y.chain(Bar_c).chain(Quaz_r)),
*my_type.project_to(&foo.y.chain(bar.c).chain(quaz.r)),
5
);
}

0 comments on commit 778d0d0

Please sign in to comment.