-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
still missing enum support though
- Loading branch information
Showing
4 changed files
with
427 additions
and
375 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() }, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.