Skip to content

Commit

Permalink
Merge pull request #4 from hoodie/feature/update-syn
Browse files Browse the repository at this point in the history
update syn&quote
  • Loading branch information
koivunej committed Jan 8, 2022
2 parents 4a6629e + 8d4eeaa commit a756d6d
Show file tree
Hide file tree
Showing 12 changed files with 514 additions and 401 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Expand Up @@ -6,10 +6,12 @@ description = "Custom derives to help with types containing Cow fields"
license = "MIT"
repository = "https://github.com/koivunej/derive-into-owned"
homepage = "https://github.com/koivunej/derive-into-owned"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = "0.11"
quote = "0.3"
syn = "1"
quote = "1"
proc-macro2 = "1"
92 changes: 92 additions & 0 deletions src/field_kind.rs
@@ -0,0 +1,92 @@
use quote::{format_ident, quote};

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

#[derive(Debug)]
pub enum FieldKind {
PlainCow,
AssumedCow,
/// Option fields with either PlainCow or AssumedCow
OptField(usize, Box<FieldKind>),
IterableField(Box<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
}
} else {
FieldKind::JustMoved
}
}

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) => {
let next = format_ident!("val");
let next = quote! { #next };

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

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

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

let tokens = inner.move_or_clone_field(&next);

quote! { #var.into_iter().map(|x| #tokens).collect() }
}
JustMoved => quote! { #var },
}
}

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()) },
AssumedCow => quote! { #var.borrowed() },
OptField(levels, ref 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) };
}

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

let tokens = inner.borrow_or_clone(&next);

quote! { #var.iter().map(|x| #tokens).collect() }
}
JustMoved => quote! { #var.clone() },
}
}
}
168 changes: 168 additions & 0 deletions src/helpers.rs
@@ -0,0 +1,168 @@
use crate::field_kind::FieldKind;

pub fn has_lifetime_arguments(segments: &[syn::PathSegment]) -> bool {
if let Some(&syn::PathArguments::AngleBracketed(ref generics)) =
segments.last().map(|x| &x.arguments)
{
generics
.args
.iter()
.any(|f| matches!(f, syn::GenericArgument::Lifetime(_)))
} else {
false
}
}

pub fn number_of_type_arguments(segments: &[syn::PathSegment]) -> usize {
if let Some(&syn::PathArguments::AngleBracketed(ref generics)) =
segments.last().map(|x| &x.arguments)
{
generics
.args
.iter()
.filter(|f| matches!(f, syn::GenericArgument::Type(_)))
.count()
} else {
0
}
}

pub fn has_binding_arguments(segments: &[syn::PathSegment]) -> bool {
if let Some(&syn::PathArguments::AngleBracketed(ref generics)) =
segments.last().map(|x| &x.arguments)
{
generics
.args
.iter()
.any(|f| matches!(f, syn::GenericArgument::Binding(_)))
} else {
false
}
}

fn type_hopefully_is(segments: &[syn::PathSegment], expected: &str) -> bool {
let expected = expected
.split("::")
.map(|x| quote::format_ident!("{}", x))
.collect::<Vec<_>>();
if segments.len() > expected.len() {
return false;
}

let expected = expected.iter().collect::<Vec<_>>();
let segments = segments.iter().map(|x| &x.ident).collect::<Vec<_>>();

for len in 0..expected.len() {
if segments[..] == expected[expected.len() - len - 1..] {
return true;
}
}

false
}

pub fn is_cow(segments: &[syn::PathSegment]) -> bool {
type_hopefully_is(segments, "std::borrow::Cow")
}

pub fn is_cow_alike(segments: &[syn::PathSegment]) -> bool {
if let Some(&syn::PathArguments::AngleBracketed(ref _data)) =
segments.last().map(|x| &x.arguments)
{
has_lifetime_arguments(segments)
} else {
false
}
}

pub fn collect_segments(path: &syn::Path) -> Vec<syn::PathSegment> {
path.segments.iter().cloned().collect::<Vec<_>>()
}

pub fn is_opt_cow(mut segments: Vec<syn::PathSegment>) -> Option<FieldKind> {
let mut levels = 0;
loop {
if type_hopefully_is(&segments, "std::option::Option") {
if let syn::PathSegment {
arguments: syn::PathArguments::AngleBracketed(ref data),
..
} = *segments.last().expect("last segment")
{
if has_lifetime_arguments(&segments) || has_binding_arguments(&segments) {
// Option<&'a ?> cannot be moved but let the compiler complain
// don't know about data bindings
break;
}

if number_of_type_arguments(&segments) != 1 {
// Option<A, B> probably means some other, movable option
break;
}

match *data.args.first().expect("first arg") {
syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {
// segments: ref next_segments,
ref path,
..
})) => {
levels += 1;
segments = collect_segments(path);
continue;
}
_ => break,
}
}
} else if is_cow(&segments) {
return Some(FieldKind::OptField(levels, Box::new(FieldKind::PlainCow)));
} else if is_cow_alike(&segments) {
return Some(FieldKind::OptField(levels, Box::new(FieldKind::AssumedCow)));
}

break;
}

None
}

pub fn is_iter_field(mut segments: Vec<syn::PathSegment>) -> Option<FieldKind> {
loop {
// this should be easy to do for arrays as well..
if type_hopefully_is(&segments, "std::vec::Vec") {
if let syn::PathSegment {
arguments: syn::PathArguments::AngleBracketed(ref data),
..
} = *segments.last().expect("last segment")
{
if has_lifetime_arguments(&segments) || has_binding_arguments(&segments) {
break;
}

// if data.types.len() != 1 {
if number_of_type_arguments(&segments) != 1 {
// TODO: this could be something like Vec<(u32, Bar<'a>)>?
break;
}

match *data.args.first().expect("first arg") {
syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {
// segments: ref next_segments,
ref path,
..
})) => {
segments = collect_segments(path);
continue;
}
_ => break,
}
}
} else if is_cow(&segments) {
return Some(FieldKind::IterableField(Box::new(FieldKind::PlainCow)));
} else if is_cow_alike(&segments) {
return Some(FieldKind::IterableField(Box::new(FieldKind::AssumedCow)));
}

break;
}

None
}

0 comments on commit a756d6d

Please sign in to comment.