Skip to content

Commit

Permalink
adds variant skip attribute to derive macro
Browse files Browse the repository at this point in the history
  • Loading branch information
astrale-sharp committed Jun 13, 2023
1 parent 533a8d6 commit 20ec055
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 171 deletions.
356 changes: 228 additions & 128 deletions godot-macros/src/derive_from_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::util::{decl_get_info, DeclInfo};
use crate::util::{decl_get_info, has_attr, DeclInfo};
use crate::ParseResult;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use venial::{Declaration, StructFields};
use quote::{format_ident, quote, ToTokens};
use venial::{Declaration, NamedStructFields, StructFields, TupleField, TupleStructFields};

pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
let DeclInfo {
Expand All @@ -24,69 +24,12 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {

match decl {
Declaration::Struct(s) => match s.fields {
venial::StructFields::Unit => {
body = quote! {
#body
return Ok(Self);
}
}
venial::StructFields::Tuple(fields) => {
if fields.fields.len() == 1 {
body = quote! {
#body
let root = root.try_to()?;
Ok(Self(root))
};
} else {
let ident_and_set = fields.fields.iter().enumerate().map(|(k, _)| {
let ident = format_ident!("__{}", k);
(
ident.clone(),
quote! {
let #ident = root.pop_front().ok_or(godot::builtin::VariantConversionError::MissingValue)?;
},

)
});
let (idents, ident_set): (Vec<_>, Vec<_>) = ident_and_set.unzip();
body = quote! {
#body
let mut root = root.try_to::<godot::builtin::Array<godot::builtin::Variant>>()?;
#(
#ident_set

)*
Ok(Self(
#(#idents.try_to()?,)*
))
};
}
}
venial::StructFields::Named(fields) => {
let fields = fields.fields.iter().map(|(field, _)|{
let ident = &field.name;
let string_ident = &field.name.to_string();
(
quote!{
let #ident = root.get(#string_ident).ok_or(godot::builtin::VariantConversionError::MissingValue)?;
},

quote!{
#ident :#ident.try_to()?
}
)

});
let (set_idents, set_self): (Vec<_>, Vec<_>) = fields.unzip();
body = quote! {
#body
let root = root.try_to::<godot::builtin::Dictionary>()?;
#(
#set_idents
)*
Ok(Self{ #(#set_self,)* })
}
venial::StructFields::Unit => make_unit_struct(&mut body),
venial::StructFields::Tuple(fields) if fields.fields.len() == 1 => {
make_new_type_struct(&mut body, fields)
}
venial::StructFields::Tuple(fields) => make_tuple_struct(fields, &mut body, &name),
venial::StructFields::Named(fields) => make_named_struct(fields, &mut body, &name),
},
Declaration::Enum(enum_) => {
if enum_.variants.is_empty() {
Expand All @@ -99,78 +42,41 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
let variant_name = enum_v.name.clone();
let variant_name_string = enum_v.name.to_string();
let if_let_content = match &enum_v.contents {
StructFields::Unit => quote! {
_ if has_attr(&enum_v.attributes, "variant", "skip") => {
quote! {
if root == Variant::nil() {
return Ok(Self::default() );
}
}
}
StructFields::Unit if !has_attr(&enum_v.attributes, "variant", "skip") => {
quote! {
let child = root.try_to::<String>();
if child == Ok(String::from(#variant_name_string)) {
return Ok(Self::#variant_name);
}
},
StructFields::Tuple(fields) => {
if fields.fields.len() == 1 {
let (field, _) = fields.fields.first().unwrap();
let field_type = &field.ty;
quote! {
let child = root.try_to::<godot::builtin::Dictionary>();
if let Ok(child) = child {
if let Some(variant) = child.get(#variant_name_string) {
return Ok(Self::#variant_name(variant.try_to::<#field_type>()?));
}
}
}
} else {
let fields = fields.fields.iter().enumerate()
.map(|(k, (field, _))|{
let ident = format_ident!("__{k}");
let field_type = &field.ty;
(
quote!{#ident},

quote!{
let #ident = variant
.pop_front()
.ok_or(godot::builtin::VariantConversionError::MissingValue)?
.try_to::<#field_type>()?;
})
});
let (idents, set_idents): (Vec<_>, Vec<_>) = fields.unzip();

quote! {
let child = root.try_to::<godot::builtin::Dictionary>();
if let Ok(child) = child {
if let Some(variant) = child.get(#variant_name_string) {
let mut variant = variant.try_to::<godot::builtin::Array<godot::builtin::Variant>>()?;
#(#set_idents)*
return Ok(Self::#variant_name(#(#idents ,)*));
}
}
}
}
}
StructFields::Named(fields) => {
let fields = fields.fields.iter().map(|(field, _)| {
let field_name = &field.name;
let field_name_string = &field.name.to_string();
let field_type = &field.ty;
(
quote!{#field_name},
quote!{
let #field_name = variant.get(#field_name_string).ok_or(godot::builtin::VariantConversionError::MissingValue)?.try_to::<#field_type>()?;
}
)
});
let (fields, set_fields): (Vec<_>, Vec<_>) = fields.unzip();
quote! {
if let Ok(root) = root.try_to::<godot::builtin::Dictionary>() {
if let Some(variant) = root.get(#variant_name_string) {
let variant = variant.try_to::<godot::builtin::Dictionary>()?;
#(
#set_fields
)*
return Ok(Self::#variant_name{ #(#fields,)* });
}
StructFields::Unit => quote! {},
StructFields::Tuple(fields) if fields.fields.len() == 1 => {
let (field, _) = fields.fields.first().unwrap();
match has_attr(&field.attributes, "variant", "skip") {
true => make_enum_new_type_skipped(
field,
&variant_name,
&variant_name_string,
),
false => {
make_enum_new_type(field, &variant_name, &variant_name_string)
}
}
}
StructFields::Tuple(fields) => {
make_enum_tuple(fields, &variant_name, &variant_name_string)
}
StructFields::Named(fields) => {
make_enum_named(fields, &variant_name, &variant_name_string)
}
};
matches = quote! {
#matches
Expand Down Expand Up @@ -198,3 +104,197 @@ pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
}
})
}

fn make_named_struct(
fields: venial::NamedStructFields,
body: &mut TokenStream,
name: &impl ToTokens,
) {
let fields = fields.fields.iter().map(|(field, _)|{
let ident = &field.name;
let string_ident = &field.name.to_string();
match has_attr(&field.attributes, "variant", "skip") {
true => (
quote!{},
quote!{ #ident : #name::default().#ident }
),
false => (
quote!{
let #ident = root.get(#string_ident).ok_or(godot::builtin::VariantConversionError::MissingValue)?;
},

quote!{ #ident :#ident.try_to()? }
),
}


});
let (set_idents, set_self): (Vec<_>, Vec<_>) = fields.unzip();
*body = quote! {
#body
let root = root.try_to::<godot::builtin::Dictionary>()?;
#(
#set_idents
)*
Ok(Self{ #(#set_self,)* })
}
}

fn make_tuple_struct(
fields: venial::TupleStructFields,
body: &mut TokenStream,
name: &impl ToTokens,
) {
let ident_and_set = fields.fields.iter().enumerate()
.map(|(k, (f,_))| {
let ident = format_ident!("__{}", k);
let field_type = f.ty.to_token_stream();
(
ident.clone(),
match has_attr(&f.attributes, "variant", "skip") {
true => quote!{
let #ident = <#name as Default>::default().#ident;
},
false => quote! {
let #ident = root.pop_front().ok_or(godot::builtin::VariantConversionError::MissingValue)?.try_to::<#field_type>()?;
},
},

)
});
let (idents, ident_set): (Vec<_>, Vec<_>) = ident_and_set.unzip();
*body = quote! {
#body
let mut root = root.try_to::<godot::builtin::VariantArray>()?;
#(
#ident_set

)*
Ok(Self(
#(#idents,)*
))
};
}

fn make_new_type_struct(body: &mut TokenStream, fields: venial::TupleStructFields) {
*body = match has_attr(
&fields.fields.first().unwrap().0.attributes,
"variant",
"skip",
) {
true => quote! { Ok(Self::default()) },
false => quote! {
#body
let root = root.try_to()?;
Ok(Self(root))
},
};
}

fn make_unit_struct(body: &mut TokenStream) {
*body = quote! {
#body
return Ok(Self);
}
}

fn make_enum_new_type(
field: &TupleField,
variant_name: &impl ToTokens,
variant_name_string: &impl ToTokens,
) -> TokenStream {
let field_type = &field.ty;
quote! {
if let Ok(child) = root.try_to::<godot::builtin::Dictionary>() {
if let Some(variant) = child.get(#variant_name_string) {
return Ok(Self::#variant_name(variant.try_to::<#field_type>()?));
}
}
}
}

fn make_enum_new_type_skipped(
field: &TupleField,
variant_name: &impl ToTokens,
variant_name_string: &impl ToTokens,
) -> TokenStream {
let field_type = &field.ty;
quote! {
if let Ok(child) = root.try_to::<godot::builtin::Dictionary>() {
if child.get(#variant_name_string).is_some_and(|v|v.is_nil()) {
return Ok(Self::#variant_name(<#field_type as Default>::default()));
}
}
}
}

fn make_enum_tuple(
fields: &TupleStructFields,
variant_name: &impl ToTokens,
variant_name_string: &impl ToTokens,
) -> TokenStream {
let fields = fields.fields.iter().enumerate().map(|(k, (field, _))| {
let ident = format_ident!("__{k}");
let field_type = &field.ty;
let set_ident = match has_attr(&field.attributes, "variant", "skip") {
true => quote! {
let #ident = <#field_type as Default>::default();
},
false => quote! {
let #ident = variant.pop_front()
.ok_or(godot::builtin::VariantConversionError::MissingValue)?
.try_to::<#field_type>()?;
},
};
(ident.to_token_stream(), set_ident)
});
let (idents, set_idents): (Vec<_>, Vec<_>) = fields.unzip();

quote! {
let child = root.try_to::<godot::builtin::Dictionary>();
if let Ok(child) = child {
if let Some(variant) = child.get(#variant_name_string) {
let mut variant = variant.try_to::<godot::builtin::VariantArray>()?;
#(#set_idents)*
return Ok(Self::#variant_name(#(#idents ,)*));
}
}
}
}
fn make_enum_named(
fields: &NamedStructFields,
variant_name: &impl ToTokens,
variant_name_string: &impl ToTokens,
) -> TokenStream {
let fields = fields.fields.iter().map(|(field, _)| {
let field_name = &field.name;
let field_name_string = &field.name.to_string();
let field_type = &field.ty;
let set_field = match has_attr(&field.attributes,"variant","skip") {
true => quote!{
let #field_name = <#field_type as Default>::default();
},
false => quote! {
let #field_name = variant.get(#field_name_string)
.ok_or(godot::builtin::VariantConversionError::MissingValue)?
.try_to::<#field_type>()?;
},
};
(
field_name.to_token_stream(),
set_field
)
});
let (fields, set_fields): (Vec<_>, Vec<_>) = fields.unzip();
quote! {
if let Ok(root) = root.try_to::<godot::builtin::Dictionary>() {
if let Some(variant) = root.get(#variant_name_string) {
let variant = variant.try_to::<godot::builtin::Dictionary>()?;
#(
#set_fields
)*
return Ok(Self::#variant_name{ #(#fields,)* });
}
}
}
}
Loading

0 comments on commit 20ec055

Please sign in to comment.