Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for more complex types #18

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ Originally based off of [deep-clone-derive](https://github.com/asajeffrey/deep-c
* normal [structs](./tests/struct.rs)
* enums with tuple variants [tuple enums](./tests/simple_enum.rs)
* `IntoOwned` alike fields (actually assumes all fields with types with lifetimes are `IntoOwned` alike)
* [options of Cow or Cow-like types](./tests/opt_field.rs) `Option<Cow<'a, str>>` and `Option<Foo<'a>>`
* [vectors of Cow or Cow-like types](./tests/vec.rs)
* `Cow<'a, B>`
* [`Option<T>`](./tests/opt_field.rs)
* [`Vec<T>`](./tests/vec.rs)
* `Box<T>`
* [arrays](./tests/array.rs)
* [tuples](./tests/tuple.rs)
* arbitrarily nested types built from the above ([Example 1](./tests/nested_types.rs), [Example 2](./tests/triple_cow.rs))

But wait there is even more! `[derive(Borrowed)]` generates a currently perhaps a bit limited version of a method like:

Expand All @@ -59,9 +64,7 @@ Note, there's no trait implementation expected because I didn't find one at the
Currently deriving will fail miserably for at least but not limited to:

* `IntoOwned`: borrowed fields like `&'a str`
* `Borrowed`: struct/enum has more than one lifetime
* both: arrays not supported
* both: into_owned/borrowed types inside tuples inside vectors
* `Borrowed`: struct/enum has multiple lifetimes that depend on each other (explicitly or implicitly)

Using with incompatible types results in not so understandable error messages. For example, given a struct:

Expand Down
170 changes: 126 additions & 44 deletions src/field_kind.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,126 @@
use quote::{format_ident, quote};

use crate::helpers::{collect_segments, is_cow, is_cow_alike, is_iter_field, is_opt_cow};
use crate::helpers::{collect_segments, cow_field, generic_field, is_cow_alike};

#[derive(Debug)]
pub enum FieldKind {
PlainCow,
PlainCow(Box<FieldKind>),
AssumedCow,
/// Option fields with either PlainCow or AssumedCow
OptField(usize, Box<FieldKind>),
OptField(Box<FieldKind>),
IterableField(Box<FieldKind>),
Box(Box<FieldKind>),
Array(Box<FieldKind>),
Tuple(Vec<FieldKind>),
JustMoved,
}
impl FieldKind {
pub fn resolve(ty: &syn::Type) -> Self {
if let syn::Type::Path(syn::TypePath { ref path, .. }) = ty {
if is_cow(&collect_segments(path)) {
FieldKind::PlainCow
} else if is_cow_alike(&collect_segments(path)) {
FieldKind::AssumedCow
} else if let Some(kind) = is_opt_cow(collect_segments(path)) {
kind
} else if let Some(kind) = is_iter_field(collect_segments(path)) {
kind
} else {
FieldKind::JustMoved
let field_kind = match ty {
syn::Type::Path(syn::TypePath { path, .. }) => {
let segments = collect_segments(path);

if let Some(kind) = cow_field(&segments) {
FieldKind::PlainCow(Box::new(kind))
} else if is_cow_alike(&segments) {
FieldKind::AssumedCow
} else if let Some(kind) = generic_field(&segments, "std::option::Option") {
FieldKind::OptField(Box::new(kind))
} else if let Some(kind) = generic_field(&segments, "std::vec::Vec") {
FieldKind::IterableField(Box::new(kind))
} else if let Some(kind) = generic_field(&segments, "std::boxed::Box") {
FieldKind::Box(Box::new(kind))
} else {
FieldKind::JustMoved
}
}
syn::Type::Array(syn::TypeArray { elem, .. }) => {
FieldKind::Array(Box::new(FieldKind::resolve(elem)))
}
syn::Type::Tuple(syn::TypeTuple { elems, .. }) => {
if elems.is_empty() {
// Unit
FieldKind::JustMoved
} else {
FieldKind::Tuple(elems.iter().map(FieldKind::resolve).collect())
}
}
} else {
FieldKind::JustMoved
_ => FieldKind::JustMoved,
};

// Optimization to shortcut to JustMoved for containers that contain no
// references (thus generating a single move / clone instead of handling
// all inner fields)
if field_kind.is_static() {
return FieldKind::JustMoved;
}

field_kind
}

fn is_static(&self) -> bool {
match self {
FieldKind::PlainCow(_) => false,
FieldKind::AssumedCow => false,
FieldKind::OptField(inner) => inner.is_static(),
FieldKind::IterableField(inner) => inner.is_static(),
FieldKind::Box(inner) => inner.is_static(),
FieldKind::Array(inner) => inner.is_static(),
FieldKind::Tuple(elems) => elems.iter().all(FieldKind::is_static),
FieldKind::JustMoved => true,
}
}

pub fn move_or_clone_field(&self, var: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
use self::FieldKind::*;

match *self {
PlainCow => quote! { ::std::borrow::Cow::Owned(#var.into_owned()) },
AssumedCow => quote! { #var.into_owned() },
OptField(levels, ref inner) => {
match self {
PlainCow(inner) => {
let next = format_ident!("val");
let next = quote! { #next };

let mut tokens = inner.move_or_clone_field(&next);
let tokens = inner.move_or_clone_field(&quote! { #next });

for _ in 0..(levels - 1) {
tokens = quote! { #next.map(|#next| #tokens) };
quote! {
{
let #next = ::std::borrow::Cow::into_owned(#var);
::std::borrow::Cow::Owned(#tokens)
}
}
}
AssumedCow => quote! { #var.into_owned() },
OptField(inner) => {
let next = format_ident!("val");
let tokens = inner.move_or_clone_field(&quote! { #next });

quote! { #var.map(|#next| #tokens) }
}
IterableField(ref inner) => {
IterableField(inner) => {
let next = format_ident!("x");
let next = quote! { #next };
let tokens = inner.move_or_clone_field(&quote! { #next });

let tokens = inner.move_or_clone_field(&next);
quote! { #var.into_iter().map(|#next| #tokens).collect() }
}
Box(inner) => {
let tokens = inner.move_or_clone_field(&quote! { (*#var) });

quote! { #var.into_iter().map(|x| #tokens).collect() }
quote! { ::std::boxed::Box::new(#tokens) }
}
Array(inner) => {
let next = format_ident!("x");
let tokens = inner.move_or_clone_field(&quote! { #next });

quote! { #var.map(|#next| #tokens) }
}
Tuple(fields) => {
let next = format_ident!("val");
let fields = fields.iter().enumerate().map(|(index, field)| {
let index = syn::Index::from(index);
field.move_or_clone_field(&quote! { #next.#index })
});
quote! {
{
let #next = #var;
( #(#fields),* , )
}
}
}
JustMoved => quote! { #var },
}
Expand All @@ -63,28 +129,44 @@ impl FieldKind {
pub fn borrow_or_clone(&self, var: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
use self::FieldKind::*;

match *self {
PlainCow => quote! { ::std::borrow::Cow::Borrowed(#var.as_ref()) },
match self {
PlainCow(_) => quote! { ::std::borrow::Cow::Borrowed(#var.as_ref()) },
AssumedCow => quote! { #var.borrowed() },
OptField(levels, ref inner) => {
OptField(inner) => {
let next = format_ident!("val");
let next = quote! { #next };

let mut tokens = inner.borrow_or_clone(&next);

for _ in 0..(levels - 1) {
tokens = quote! { #next.as_ref().map(|#next| #tokens) };
}
let tokens = inner.borrow_or_clone(&quote! { #next });

quote! { #var.as_ref().map(|#next| #tokens) }
}
IterableField(ref inner) => {
IterableField(inner) => {
let next = format_ident!("x");
let next = quote! { #next };
let tokens = inner.borrow_or_clone(&quote! { #next });

let tokens = inner.borrow_or_clone(&next);
quote! { #var.iter().map(|#next| #tokens).collect() }
}
Box(inner) => {
let tokens = inner.borrow_or_clone(&quote! { #var.as_ref() });

quote! { ::std::boxed::Box::new(#tokens) }
}
Array(inner) => {
let next = format_ident!("x");
let tokens = inner.borrow_or_clone(&quote! { #next });

quote! { #var.iter().map(|x| #tokens).collect() }
quote! { #var.each_ref().map(|#next| #tokens) }
}
Tuple(fields) => {
let next = format_ident!("val");
let fields = fields.iter().enumerate().map(|(index, field)| {
let index = syn::Index::from(index);
field.borrow_or_clone(&quote! { (&#next.#index) })
});
quote! {
{
let #next = #var;
( #(#fields),* , )
}
}
}
JustMoved => quote! { #var.clone() },
}
Expand Down
Loading