Alright, so as a follow-up, I can make the code work, but it feels more difficult than it needs to be. In order to deal with the issue of recognizing specific types, I just force the user to specify the type by hand through an attribute. Is there a more elegant way to make this work? The macro is specified as:
// External dependencies
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Data, DeriveInput, Fields,
Lit::Str,
MetaNameValue, Token, Type,
};
// Parses options of the form (option="value",)*
struct Options(Punctuated<MetaNameValue, Token![,]>);
impl Parse for Options {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
syn::parenthesized!(content in input);
let param: Punctuated<MetaNameValue, Token![,]> =
content.parse_terminated(MetaNameValue::parse)?;
Ok(Options(param))
}
}
// Define a derive macro
#[proc_macro_derive(Vectorize, attributes(vectorize))]
pub fn vectorize_macro_derive(input: TokenStream) -> TokenStream {
// Parse the target of the declare macro
let ast = parse_macro_input!(input as DeriveInput);
// Grab the name of the struct
let name = &ast.ident;
// Look for the vectorize attribute that contains our options
let attr = ast
.attrs
.iter()
.filter(|a| a.path.is_ident("vectorize"))
.nth(0)
.expect("vectorize attribute required for deriving Vectorize");
// Parse our options
let options: Options = syn::parse2(attr.tokens.clone()).expect(
"vectorize attribute must be in the form \
\"vectorize((option = \"value\",)*)\"",
);
// List of valid options
let valid = vec!["base"];
// Throw an error if we have any invalid options
options.0.iter().for_each(|option| {
// Requires the path to match a string in valid
if !valid.iter().any(|&v| option.path.is_ident(v)) {
// At this point we have a problem, so turn the path into something
// that can be displayed to the user
let path = option.path.clone();
let path = quote!(#path);
panic!("{} is not a valid option for attribute vectorize", path);
}
});
// Extract the base type
let base_type = options
.0
.iter()
.filter(|option| option.path.is_ident("base"))
.nth(0)
.unwrap()
.lit
.clone();
let base_type = if let Str(s) = base_type {
syn::parse_str::<Type>(&s.value()).expect(
"argument to option base in attribute vectorize must \
be a string convertible to a type",
)
} else {
panic!(
"argument to option base in attribute vectorized must \
be a string convertable to a type"
)
};
// Determine the vectorized type
let vec_type = quote!(Vec <#base_type>).into();
let vec_type = parse_macro_input!(vec_type as Type);
// Extract all fields labeled with the vectorize attribute
let vectorized: Vec<_> = if let Data::Struct(ref s) = ast.data {
// Process the struct fields
match s.fields {
// Named
Fields::Named(ref fields) => {
// Accumulate the fields that contain the vectorize attribute
fields
.named
.iter()
.filter(|field| {
field.attrs.iter().any(|a| a.path.is_ident("vectorize"))
})
.cloned()
.map(|field| {
let name = field.ident.unwrap();
let ty = field.ty;
if ty != base_type && ty != vec_type {
panic!(
"vectorize attribute applied to field {} of \
type {}, but the vectorize attribute \
requires the types to be either {} or {}",
quote!(#name),
quote!(#ty),
quote!(#base_type),
quote!(#vec_type)
);
}
(name, ty)
})
.collect()
}
// All other
_ => panic!(
"Can only use Vectorize derive with structs that \
contain named fields"
),
}
// Panic when we don't have a struct
} else {
panic!("Can only Vectorize derive macro on structs")
};
// Accumulate the commands to push the fields into a vector
let myinto: Vec<_> = vectorized
.iter()
.cloned()
.map(|v| {
let name = v.0;
if v.1 == base_type {
quote!(vec.push(self.#name);)
} else {
quote!(vec.extend(self.#name);)
}
})
.collect();
// Accumulate the commands to take elements from a vector
let myfrom: Vec<_> = vectorized
.iter()
.cloned()
.map(|v| {
let name = v.0;
if v.1 == base_type {
quote!(self.#name = v.pop().unwrap();)
} else {
quote!(self.#name = v.drain(0..self.#name.len()).collect();)
}
})
.collect();
// Generate the code
let generics_params = ast.generics.params;
let generics_where = ast.generics.where_clause;
let gen = quote! {
trait Vectorize <#generics_params> #generics_where {
fn to_vec(self) -> #vec_type;
fn from_vec(self,v : #vec_type) -> Self;
}
impl <#generics_params> Vectorize <#generics_params>
for #name <#generics_params>
#generics_where
{
fn to_vec(self) -> #vec_type {
let mut vec = Vec::new();
#(#myinto)*
vec
}
fn from_vec(mut self,mut v : #vec_type) -> Self {
#(#myfrom)*
self
}
}
};
gen.into()
}
This allows code such as:
// External dependencies
use mylib::{Vectorize};
#[derive(Vectorize, Debug, Clone)]
#[vectorize(base = "T")]
struct MyStruct <T>
where
T : std::fmt::Debug
{
#[vectorize]
x: Vec<T>,
y: bool,
z: T,
#[vectorize]
w: T,
s: String,
v: Vec<f64>,
}
fn main() {
let foo = MyStruct {
x: vec![1.2, 2.3, 3.4, 4.5],
y: true,
z: 5.6,
w: 6.7,
s: "I like pie!".to_string(),
v: vec![7.8, 9.10],
};
println!("{:?}", foo);
let vec = foo.clone().to_vec();
println!("{:?}", vec);
let foo = foo.from_vec(vec.iter().cloned().map(|x| x * 2.0).collect());
println!("{:?}", foo);
}
which gives:
MyStruct { x: [1.2, 2.3, 3.4, 4.5], y: true, z: 5.6, w: 6.7, s: "I like pie!", v: [7.8, 9.1] }
[1.2, 2.3, 3.4, 4.5, 6.7]
MyStruct { x: [2.4, 4.6, 6.8, 9.0], y: true, z: 5.6, w: 13.4, s: "I like pie!", v: [7.8, 9.1] }