Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions core/src/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
//! let d_person: DomainPerson = frunk::convert_from(a_person); // done
//! # }

use crate::coproduct::Coproduct;
use crate::hlist::HNil;

/// A trait that converts from a type to a generic representation.
///
/// For the most part, you should be using the derivation that is available
Expand Down Expand Up @@ -120,6 +123,74 @@ pub trait Generic {
}
}

type UnaryVariant<T> = crate::HList!(T);
type GenericOptionRepr<T> = crate::Coprod!(HNil, UnaryVariant<T>);
type GenericResultRepr<T, E> = crate::Coprod!(UnaryVariant<T>, UnaryVariant<E>);
type GenericBoolRepr = crate::Coprod!(HNil, HNil);

Comment thread
lloydmeta marked this conversation as resolved.
impl<T> Generic for Option<T> {
type Repr = GenericOptionRepr<T>;

#[inline(always)]
fn into(self) -> Self::Repr {
match self {
None => Coproduct::Inl(crate::hlist![]),
Some(value) => Coproduct::Inr(Coproduct::Inl(crate::hlist![value])),
}
}

#[inline(always)]
fn from(repr: Self::Repr) -> Self {
match repr {
Coproduct::Inl(crate::hlist_pat![]) => None,
Coproduct::Inr(Coproduct::Inl(crate::hlist_pat![value])) => Some(value),
Coproduct::Inr(Coproduct::Inr(cnil)) => match cnil {},
}
}
}

impl<T, E> Generic for Result<T, E> {
type Repr = GenericResultRepr<T, E>;

#[inline(always)]
fn into(self) -> Self::Repr {
match self {
Ok(value) => Coproduct::Inl(crate::hlist![value]),
Err(value) => Coproduct::Inr(Coproduct::Inl(crate::hlist![value])),
}
}

#[inline(always)]
fn from(repr: Self::Repr) -> Self {
match repr {
Coproduct::Inl(crate::hlist_pat![value]) => Ok(value),
Coproduct::Inr(Coproduct::Inl(crate::hlist_pat![value])) => Err(value),
Coproduct::Inr(Coproduct::Inr(cnil)) => match cnil {},
}
}
}

impl Generic for bool {
type Repr = GenericBoolRepr;

#[inline(always)]
fn into(self) -> Self::Repr {
match self {
false => Coproduct::Inl(crate::hlist![]),
true => Coproduct::Inr(Coproduct::Inl(crate::hlist![])),
}
}

#[inline(always)]
fn from(repr: Self::Repr) -> Self {
match repr {
Coproduct::Inl(crate::hlist_pat![]) => false,
Coproduct::Inr(Coproduct::Inl(crate::hlist_pat![])) => true,
Coproduct::Inr(Coproduct::Inr(cnil)) => match cnil {},
}
}
}

/// Given a generic representation `Repr` of a `Dst`, returns `Dst`.
pub fn from_generic<Dst, Repr>(repr: Repr) -> Dst
where
Expand Down
122 changes: 121 additions & 1 deletion core/src/labelled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,11 @@
//! # }
//! ```

use crate::coproduct::Coproduct;
use crate::hlist::*;
use crate::indices::*;
use crate::traits::ToRef;
use chars::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -282,6 +284,125 @@ where
}
}

type NoneLabel = (N, o, n, e);
type SomeLabel = (S, o, m, e);
type OkLabel = (O, k);
type ErrLabel = (E, r, r);
type FalseLabel = (f, a, l, s, e);
type TrueLabel = (t, r, u, e);
type TupleField0 = (__, _0);
type UnitVariant<Name> = Field<Name, HNil>;
type UnaryTupleVariant<Name, T> = Field<Name, crate::HList!(Field<TupleField0, T>)>;
type LabelledOptionRepr<T> =
crate::Coprod!(UnitVariant<NoneLabel>, UnaryTupleVariant<SomeLabel, T>);
type LabelledResultRepr<T, E> =
crate::Coprod!(UnaryTupleVariant<OkLabel, T>, UnaryTupleVariant<ErrLabel, E>);
type LabelledBoolRepr = crate::Coprod!(UnitVariant<FalseLabel>, UnitVariant<TrueLabel>);

#[inline(always)]
fn labelled_tuple_field_0<T>(value: T) -> Field<TupleField0, T> {
crate::field!(TupleField0, value, "_0")
}

#[inline(always)]
fn labelled_unit_variant<Name>(name: &'static str) -> UnitVariant<Name> {
crate::field!(Name, crate::hlist![], name)
}

#[inline(always)]
fn labelled_unary_tuple_variant<Name, T>(
name: &'static str,
value: T,
) -> UnaryTupleVariant<Name, T> {
crate::field!(Name, crate::hlist![labelled_tuple_field_0(value)], name)
}

impl<T> LabelledGeneric for Option<T> {
type Repr = LabelledOptionRepr<T>;

#[inline(always)]
fn into(self) -> Self::Repr {
match self {
None => Coproduct::Inl(labelled_unit_variant::<NoneLabel>("None")),
Some(value) => Coproduct::Inr(Coproduct::Inl(labelled_unary_tuple_variant::<
SomeLabel,
_,
>("Some", value))),
}
}
Comment thread
lloydmeta marked this conversation as resolved.

#[inline(always)]
fn from(repr: Self::Repr) -> Self {
match repr {
Coproduct::Inl(Field {
value: crate::hlist_pat![],
..
}) => None,
Coproduct::Inr(Coproduct::Inl(Field {
value: crate::hlist_pat![Field { value, .. }],
..
})) => Some(value),
Coproduct::Inr(Coproduct::Inr(cnil)) => match cnil {},
}
}
}

impl<T, E> LabelledGeneric for Result<T, E> {
type Repr = LabelledResultRepr<T, E>;

#[inline(always)]
fn into(self) -> Self::Repr {
match self {
Ok(value) => Coproduct::Inl(labelled_unary_tuple_variant::<OkLabel, _>("Ok", value)),
Err(value) => Coproduct::Inr(Coproduct::Inl(
labelled_unary_tuple_variant::<ErrLabel, _>("Err", value),
)),
}
}

#[inline(always)]
fn from(repr: Self::Repr) -> Self {
match repr {
Coproduct::Inl(Field {
value: crate::hlist_pat![Field { value, .. }],
..
}) => Ok(value),
Coproduct::Inr(Coproduct::Inl(Field {
value: crate::hlist_pat![Field { value, .. }],
..
})) => Err(value),
Coproduct::Inr(Coproduct::Inr(cnil)) => match cnil {},
}
}
}

impl LabelledGeneric for bool {
type Repr = LabelledBoolRepr;

#[inline(always)]
fn into(self) -> Self::Repr {
match self {
false => Coproduct::Inl(labelled_unit_variant::<FalseLabel>("false")),
true => Coproduct::Inr(Coproduct::Inl(labelled_unit_variant::<TrueLabel>("true"))),
}
}

#[inline(always)]
fn from(repr: Self::Repr) -> Self {
match repr {
Coproduct::Inl(Field {
value: crate::hlist_pat![],
..
}) => false,
Coproduct::Inr(Coproduct::Inl(Field {
value: crate::hlist_pat![],
..
})) => true,
Coproduct::Inr(Coproduct::Inr(cnil)) => match cnil {},
}
}
}

/// Given a labelled generic representation of a `Dst`, returns `Dst`
pub fn from_labelled_generic<Dst, Repr>(repr: Repr) -> Dst
where
Expand Down Expand Up @@ -968,7 +1089,6 @@ where

#[cfg(test)]
mod tests {
use super::chars::*;
use super::*;
use alloc::collections::{LinkedList, VecDeque};
use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
Expand Down
45 changes: 42 additions & 3 deletions derives/src/derive_generic.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use frunk_proc_macro_helpers::*;
use proc_macro::TokenStream;
use quote::ToTokens;
use std::iter::repeat;
use syn::Data;

/// Given an AST, returns an implementation of Generic using HList
/// Given an AST, returns an implementation of Generic using an HList
/// representation for structs and a Coproduct of payload HLists for enums.
///
/// Only works with Structs and Tuple Structs
/// Works with structs, tuple structs, and enums.
pub fn impl_generic(input: TokenStream) -> impl ToTokens {
let ast = to_ast(input);
let name = &ast.ident;
Expand Down Expand Up @@ -39,7 +41,44 @@ pub fn impl_generic(input: TokenStream) -> impl ToTokens {
}
}
}
_ => panic!("Only Structs are supported. Enums/Unions cannot be turned into Generics."),
Data::Enum(ref data) => {
let variant_bindings = VariantBindings::new(&data.variants);
let repr_type = &variant_bindings.build_coprod_type(VariantBinding::build_hlist_type);
let coprod_constrs =
&variant_bindings.build_coprod_constrs(VariantBinding::build_hlist_constr);
let coprod_unreachable = &variant_bindings.build_coprod_unreachable_arm(false);
let type_constrs1 =
&variant_bindings.build_variant_constrs(VariantBinding::build_type_constr);
let type_constrs2 = type_constrs1;
let name_it1 = repeat(name);
let name_it2 = repeat(name);

quote! {
#[allow(non_snake_case, non_camel_case_types)]
impl #impl_generics ::frunk_core::generic::Generic for #name #ty_generics #where_clause {

type Repr = #repr_type;

fn into(self) -> Self::Repr {
match self {
#(
#name_it1 :: #type_constrs1 => #coprod_constrs,
)*
}
}

fn from(r: Self::Repr) -> Self {
match r {
#(
#coprod_constrs => #name_it2 :: #type_constrs2,
)*
#coprod_unreachable
}
}
}
}
}
_ => panic!("Only Structs and Enums can be turned into Generics."),
};

// print!("{}", tree);
Expand Down
4 changes: 2 additions & 2 deletions derives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use crate::derive_labelled_generic::impl_labelled_generic;

use quote::ToTokens;

/// Derives a Generic instance based on HList for
/// a given Struct or Tuple Struct
/// Derives a Generic instance based on HList for structs and
/// Coproducts of payload HLists for enums.
Comment thread
lloydmeta marked this conversation as resolved.
#[proc_macro_derive(Generic)]
pub fn generic(input: TokenStream) -> TokenStream {
// Build the impl
Expand Down
6 changes: 6 additions & 0 deletions proc-macro-helpers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ impl VariantBinding {
.build_hlist_constr(FieldBinding::build_field_pat),
)
}
pub fn build_hlist_type(&self) -> TokenStream2 {
self.fields.build_hlist_type(FieldBinding::build_type)
}
pub fn build_hlist_constr(&self) -> TokenStream2 {
self.fields.build_hlist_constr(FieldBinding::build)
}
}

pub struct VariantBindings {
Expand Down
8 changes: 8 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ pub struct HasKeyword2Embedder {
#[derive(Generic, Debug, PartialEq, Eq)]
pub struct TupleStruct<'a>(pub &'a str, pub i32);

#[allow(dead_code)] // Not actually dead -- used in tests but clippy can't find it
#[derive(Generic, PartialEq, Eq, Debug)]
pub enum GenericEnum {
VariantA,
VariantB(i32),
VariantC { x: String, y: bool },
}

#[derive(LabelledGeneric)]
pub struct NormalUser<'a> {
pub first_name: &'a str,
Expand Down
Loading
Loading