From ff11540fff6107f52129afc4ab1e2ee049d105be Mon Sep 17 00:00:00 2001 From: Lili Zoey Zyli Date: Tue, 6 Feb 2024 21:17:01 +0100 Subject: [PATCH 1/2] Rewrite the `GodotConvert`, `ToGodot`, `FromGodot`, `Var`, and `Export` derive macros --- godot-macros/src/derive/data_model.rs | 348 ++++++++++++++++++ godot-macros/src/derive/derive_export.rs | 72 +--- godot-macros/src/derive/derive_from_godot.rs | 88 +++++ .../src/derive/derive_from_variant.rs | 322 ---------------- .../src/derive/derive_godot_convert.rs | 16 +- godot-macros/src/derive/derive_to_godot.rs | 86 +++++ godot-macros/src/derive/derive_to_variant.rs | 265 ------------- godot-macros/src/derive/derive_var.rs | 126 +++---- godot-macros/src/derive/mod.rs | 9 +- godot-macros/src/lib.rs | 10 +- godot-macros/src/util/mod.rs | 73 +--- itest/rust/src/object_tests/property_test.rs | 6 +- .../src/register_tests/derive_variant_test.rs | 285 ++++---------- 13 files changed, 658 insertions(+), 1048 deletions(-) create mode 100644 godot-macros/src/derive/data_model.rs create mode 100644 godot-macros/src/derive/derive_from_godot.rs delete mode 100644 godot-macros/src/derive/derive_from_variant.rs create mode 100644 godot-macros/src/derive/derive_to_godot.rs delete mode 100644 godot-macros/src/derive/derive_to_variant.rs diff --git a/godot-macros/src/derive/data_model.rs b/godot-macros/src/derive/data_model.rs new file mode 100644 index 000000000..396b01342 --- /dev/null +++ b/godot-macros/src/derive/data_model.rs @@ -0,0 +1,348 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use std::collections::HashMap; + +use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use quote::{quote, ToTokens}; +use venial::{Declaration, TyExpr}; + +use crate::{ + util::{bail, decl_get_info, ident, DeclInfo, KvParser}, + ParseResult, +}; + +pub struct GodotConvert { + pub name: Ident, + pub data: ConvertData, +} + +impl GodotConvert { + pub fn parse_declaration(declaration: Declaration) -> ParseResult { + let DeclInfo { name, where_, generic_params, .. } = decl_get_info(&declaration); + + if let Some(where_) = where_ { + return bail!(where_, "where clauses are currently not supported for `GodotConvert`") + } + + if let Some(generic_params) = generic_params { + return bail!(generic_params, "generics are currently not supported for `GodotConvert`") + } + + let data = ConvertData::parse_declaration(&declaration)?; + + Ok(Self { name, data }) + } + + pub fn name(&self) -> &Ident { + &self.name + } + + pub fn data(&self) -> &ConvertData { + &self.data + } +} + +pub enum ConvertData { + NewType { field: NewtypeField }, + Enum { variants: CStyleEnum, via: ViaType }, +} + +impl ConvertData { + pub fn parse_declaration(declaration: &Declaration) -> ParseResult { + let attribute = GodotAttribute::parse_attribute(declaration)?; + + match declaration { + Declaration::Struct(struct_) => { + if let GodotAttribute::Via { via_type: ty } = attribute { + return bail!(ty.span(), "`GodotConvert` on structs only works with `#[godot(transparent)]` currently"); + } + + Ok(Self::NewType { + field: NewtypeField::parse_struct(struct_)?, + }) + } + Declaration::Enum(enum_) => { + let via_type = match attribute { + GodotAttribute::Transparent { span } => { + return bail!( + span, + "`GodotConvert` on enums requires `#[godot(via = ..)]` currently" + ) + } + GodotAttribute::Via { via_type: ty } => ty, + }; + + Ok(Self::Enum { + variants: CStyleEnum::parse_enum(enum_)?, + via: via_type, + }) + } + _ => bail!( + declaration, + "`GodotConvert` only supports structs and enums currently" + ), + } + } + + pub fn via_type(&self) -> TokenStream { + match self { + ConvertData::NewType { field } => field.ty.to_token_stream(), + ConvertData::Enum { variants, via } => via.to_token_stream(), + } + } +} + +pub enum GodotAttribute { + Transparent { span: Span }, + Via { via_type: ViaType }, +} +impl GodotAttribute { + pub fn parse_attribute(declaration: &Declaration) -> ParseResult { + let mut parser = KvParser::parse_required(declaration.attributes(), "godot", declaration)?; + + let span = parser.span(); + + if parser.handle_alone("transparent")? { + return Ok(Self::Transparent { span }); + } + + let via_type = parser.handle_ident_required("via")?; + + let span = via_type.span(); + + let via_type = match via_type.to_string().as_str() { + "GString" => ViaType::GString(span), + "i8" => ViaType::Int(span, ViaInt::I8), + "i16" => ViaType::Int(span, ViaInt::I16), + "i32" => ViaType::Int(span, ViaInt::I32), + "i64" => ViaType::Int(span, ViaInt::I64), + "u8" => ViaType::Int(span, ViaInt::U8), + "u16" => ViaType::Int(span, ViaInt::U16), + "u32" => ViaType::Int(span, ViaInt::U32), + other => return bail!(via_type, "Via type `{}` is not supported, expected one of: GString, i8, i16, i32, i64, u8, u16, u32", other) + }; + + Ok(Self::Via { via_type }) + } + + pub fn span(&self) -> Span { + match self { + GodotAttribute::Transparent { span } => span.clone(), + GodotAttribute::Via { via_type } => via_type.span(), + } + } +} + +pub enum ViaType { + GString(Span), + Int(Span, ViaInt), +} + +impl ViaType { + fn span(&self) -> Span { + match self { + ViaType::GString(span) => span.clone(), + ViaType::Int(span, _) => span.clone(), + } + } + + fn to_token_stream(&self) -> TokenStream { + match self { + ViaType::GString(_) => quote! { ::godot::builtin::GString }, + ViaType::Int(_, int) => { + let id = int.to_ident(); + quote! { #id } + }, + } + } +} + +pub enum ViaInt { + I8, + I16, + I32, + I64, + U8, + U16, + U32, +} + +impl ViaInt { + pub fn to_ident(&self) -> Ident { + match self { + ViaInt::I8 => ident("i8" ), + ViaInt::I16 => ident("i16" ), + ViaInt::I32 => ident("i32" ), + ViaInt::I64 => ident("i64" ), + ViaInt::U8 => ident("u8" ), + ViaInt::U16 => ident("u16" ), + ViaInt::U32 => ident("u32" ), + } + } +} + +pub struct NewtypeField { + // If none, then it's the first field of a tuple-struct. + pub name: Option, + pub ty: TyExpr, +} + +impl NewtypeField { + pub fn parse_struct(struct_: &venial::Struct) -> ParseResult { + match &struct_.fields { + venial::StructFields::Unit => return bail!(&struct_.fields, "GodotConvert expects a struct with a single field, unit structs are currently not supported"), + venial::StructFields::Tuple(fields) => { + if fields.fields.len() != 1 { + return bail!(&fields.fields, "GodotConvert expects a struct with a single field, not {} fields", fields.fields.len()) + } + + let (field, _) = fields.fields[0].clone(); + + Ok(NewtypeField { name: None, ty: field.ty }) + }, + venial::StructFields::Named(fields) => { + if fields.fields.len() != 1 { + return bail!(&fields.fields, "GodotConvert expects a struct with a single field, not {} fields", fields.fields.len()) + } + + let (field, _) = fields.fields[0].clone(); + + Ok(NewtypeField { name: Some(field.name), ty: field.ty }) + }, + } + } + + pub fn field_name(&self) -> TokenStream { + match &self.name { + Some(name) => quote! { #name }, + None => quote! { 0 }, + } + } + + + +} + +#[derive(Debug, Clone)] +pub struct CStyleEnumVariant { + pub name: Ident, + pub discriminant: Option, +} + +impl CStyleEnumVariant { + pub fn parse_enum_variant(enum_variant: &venial::EnumVariant) -> ParseResult { + match enum_variant.contents { + venial::StructFields::Unit => {} + _ => { + return bail!( + &enum_variant.contents, + "GodotConvert only supports c-style enums (enums without fields)" + ) + } + } + + Ok(Self { + name: enum_variant.name.clone(), + discriminant: enum_variant.value.as_ref().map(|val| &val.value).cloned(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct CStyleEnum { + names: Vec, + discriminants: Vec, +} + +impl CStyleEnum { + pub fn parse_enum(enum_: &venial::Enum) -> ParseResult { + let variants = enum_ + .variants + .items() + .map(|enum_variant| CStyleEnumVariant::parse_enum_variant(enum_variant)) + .collect::>>()?; + + let mut key_values = Self::to_key_value_assignment(variants)? + .into_iter() + .collect::>(); + + key_values.sort_by_key(|kv| kv.0); + + let mut names = Vec::new(); + let mut discriminants = Vec::new(); + + for (key, value) in key_values.into_iter() { + names.push(value); + discriminants.push(key); + } + + Ok(Self { + names, + discriminants, + }) + } + + fn to_key_value_assignment( + variants: Vec, + ) -> ParseResult> { + let mut unassigned_names = std::collections::VecDeque::new(); + let mut discriminants = HashMap::new(); + + for variant in variants.iter() { + if let Some(disc) = &variant.discriminant { + let Ok(disc_int) = disc.to_string().parse::() else { + return bail!(disc, "expected integer literal discriminant"); + }; + + discriminants.insert(disc_int, variant.name.clone()); + } else { + unassigned_names.push_back(variant.name.clone()); + } + } + + for i in 0.. { + if unassigned_names.is_empty() { + break; + } + + if discriminants.contains_key(&i) { + continue; + } + + let name = unassigned_names.pop_front().unwrap(); + + discriminants.insert(i, name); + } + Ok(discriminants) + } + + pub fn discriminants(&self) -> &[i64] { + &self.discriminants + } + + pub fn names(&self) -> &[Ident] { + &self.names + } + + pub fn to_int_hint(&self) -> String { + self.names + .iter() + .zip(self.discriminants.iter()) + .map(|(name, discrim)| format!("{name}:{discrim}")) + .collect::>() + .join(",") + } + + pub fn to_string_hint(&self) -> String { + self.names + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + } +} diff --git a/godot-macros/src/derive/derive_export.rs b/godot-macros/src/derive/derive_export.rs index b3e939736..058717e73 100644 --- a/godot-macros/src/derive/derive_export.rs +++ b/godot-macros/src/derive/derive_export.rs @@ -5,74 +5,22 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::TokenStream; use quote::quote; -use venial::{Declaration, StructFields}; +use venial::Declaration; -use crate::util::{bail, decl_get_info, DeclInfo}; use crate::ParseResult; -pub fn derive_export(decl: Declaration) -> ParseResult { - let DeclInfo { name, .. } = decl_get_info(&decl); +use super::data_model::GodotConvert; - let enum_ = match decl { - Declaration::Enum(e) => e, - Declaration::Struct(s) => { - return bail!(s.tk_struct, "Export can only be derived on enums for now") - } - Declaration::Union(u) => { - return bail!(u.tk_union, "Export can only be derived on enums for now") - } - _ => unreachable!(), - }; - - let hint_string = if enum_.variants.is_empty() { - return bail!( - enum_.name, - "In order to derive Export, enums must have at least one variant" - ); - } else { - let mut hint_string_segments = Vec::new(); - for (enum_v, _) in enum_.variants.inner.iter() { - let v_name = enum_v.name.clone(); - let v_disc = if let Some(c) = enum_v.value.clone() { - c.value - } else { - return bail!( - v_name, - "Property can only be derived on enums with explicit discriminants in all their variants" - ); - }; - let v_disc_trimmed = v_disc - .to_string() - .trim_matches(['(', ')'].as_slice()) - .to_string(); - - hint_string_segments.push(format!("{v_name}:{v_disc_trimmed}")); - - match &enum_v.contents { - StructFields::Unit => {} - _ => { - return bail!( - v_name, - "Property can only be derived on enums with only unit variants for now" - ) - } - }; - } - hint_string_segments.join(",") - }; +pub fn derive_export(declaration: Declaration) -> ParseResult { + let GodotConvert { name, .. } = GodotConvert::parse_declaration(declaration)?; - let out = quote! { - #[allow(unused_parens)] - impl godot::register::property::Export for #name { - fn default_export_info() -> godot::register::property::PropertyHintInfo { - godot::register::property::PropertyHintInfo { - hint: godot::engine::global::PropertyHint::ENUM, - hint_string: godot::prelude::GString::from(#hint_string), - } + Ok(quote! { + impl ::godot::register::property::Export for #name { + fn default_export_info() -> ::godot::register::property::PropertyHintInfo { + <#name as ::godot::register::property::Var>::property_hint() } } - }; - Ok(out) + }) } diff --git a/godot-macros/src/derive/derive_from_godot.rs b/godot-macros/src/derive/derive_from_godot.rs new file mode 100644 index 000000000..2afcd42bc --- /dev/null +++ b/godot-macros/src/derive/derive_from_godot.rs @@ -0,0 +1,88 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::quote; +use venial::Declaration; + +use crate::derive::data_model::{ConvertData, GodotConvert, ViaType}; +use crate::ParseResult; + +use super::data_model::{CStyleEnum, NewtypeField}; + +pub fn derive_from_godot(declaration: Declaration) -> ParseResult { + let GodotConvert { name, data } = GodotConvert::parse_declaration(declaration)?; + + match data { + ConvertData::NewType { field } => from_newtype(name, field), + ConvertData::Enum { + variants, + via: ViaType::GString(_), + } => from_enum_string(name, variants), + ConvertData::Enum { + variants, + via: ViaType::Int(_, int), + } => from_enum_int(name, variants, int.to_ident()), + } +} + +fn from_newtype(name: Ident, field: NewtypeField) -> ParseResult { + // For tuple structs this ends up using the alternate tuple-struct constructor syntax of + // TupleStruct { .0: value } + let field_name = field.field_name(); + let via_type = field.ty; + + Ok(quote! { + impl ::godot::builtin::meta::FromGodot for #name { + fn try_from_godot(via: #via_type) -> ::std::result::Result { + Ok(Self { #field_name: via }) + } + } + }) +} + +fn from_enum_int(name: Ident, enum_: CStyleEnum, int: Ident) -> ParseResult { + let discriminants = enum_ + .discriminants() + .iter() + .map(|i| Literal::i64_unsuffixed(*i)) + .collect::>(); + let names = enum_.names(); + let bad_variant_error = format!("invalid {name} variant"); + + Ok(quote! { + impl ::godot::builtin::meta::FromGodot for #name { + fn try_from_godot(via: #int) -> ::std::result::Result { + match via { + #( + #discriminants => Ok(#name::#names), + )* + other => Err(::godot::builtin::meta::ConvertError::with_cause_value(#bad_variant_error, other)) + } + } + } + }) +} + +fn from_enum_string(name: Ident, enum_: CStyleEnum) -> ParseResult { + let names = enum_.names(); + let names_str = names.iter().map(ToString::to_string).collect::>(); + let bad_variant_error = format!("invalid {name} variant"); + + Ok(quote! { + impl ::godot::builtin::meta::FromGodot for #name { + fn try_from_godot(via: ::godot::builtin::GString) -> ::std::result::Result { + match via.to_string().as_str() { + #( + #names_str => Ok(#name::#names), + )* + other => Err(::godot::builtin::meta::ConvertError::with_cause_value(#bad_variant_error, other)) + } + } + } + }) +} diff --git a/godot-macros/src/derive/derive_from_variant.rs b/godot-macros/src/derive/derive_from_variant.rs deleted file mode 100644 index 750eb48ae..000000000 --- a/godot-macros/src/derive/derive_from_variant.rs +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (c) godot-rust; Bromeon and contributors. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use venial::{Declaration, NamedStructFields, StructFields, TupleField, TupleStructFields}; - -use crate::util::{decl_get_info, has_attr, DeclInfo}; -use crate::ParseResult; - -fn has_attr_skip(attributes: &[venial::Attribute]) -> bool { - has_attr(attributes, "variant", "skip") -} - -pub fn derive_from_godot(decl: Declaration) -> ParseResult { - let DeclInfo { - where_, - generic_params, - name, - name_string, - } = decl_get_info(&decl); - - let err = format!("missing expected value {name_string}"); - let mut body = quote! { - let root = { - let dict = variant.try_to::<::godot::builtin::Dictionary>()?; - let value = dict.get(#name_string); - value.ok_or(ConvertError::with_cause_value(#err, dict))? - }; - }; - - match decl { - Declaration::Struct(s) => match s.fields { - StructFields::Unit => make_unit_struct(&mut body), - StructFields::Tuple(fields) if fields.fields.len() == 1 => { - make_new_type_struct(&mut body, fields) - } - StructFields::Tuple(fields) => make_tuple_struct(fields, &mut body, &name), - StructFields::Named(fields) => make_named_struct(fields, &mut body, &name), - }, - Declaration::Enum(enum_) => { - if enum_.variants.is_empty() { - // Uninhabited enums have no values, so we cannot convert an actual Variant into them. - body = quote! { - panic!("cannot convert Variant into uninhabited enum {}", #name_string); - } - } else { - let mut matches = quote! {}; - for (enum_v, _) in &enum_.variants.inner { - let variant_name = enum_v.name.clone(); - let variant_name_string = enum_v.name.to_string(); - let if_let_content = match &enum_v.contents { - _ if has_attr_skip(&enum_v.attributes) => { - quote! { - if root == Variant::nil() { - return Ok(Self::default()); - } - } - } - StructFields::Unit if !has_attr_skip(&enum_v.attributes) => { - quote! { - let child = root.try_to::(); - if let Ok(child) = child { - if child == #variant_name_string { - return Ok(Self::#variant_name); - } - } - } - } - StructFields::Unit => quote! {}, - StructFields::Tuple(fields) if fields.fields.len() == 1 => { - let (field, _) = fields.fields.first().unwrap(); - if has_attr_skip(&field.attributes) { - make_enum_new_type_skipped( - field, - &variant_name, - &variant_name_string, - ) - } else { - 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 - #if_let_content - }; - } - body = quote! { - #body - #matches - Err(ConvertError::new()) - }; - } - } - - // decl_get_info() above ensured that no other cases are possible. - _ => unreachable!(), - } - - let gen = generic_params.as_ref().map(|x| x.as_inline_args()); - Ok(quote! { - impl #generic_params ::godot::builtin::meta::FromGodot for #name #gen #where_ { - fn try_from_godot( - variant: ::godot::builtin::Variant - ) -> Result { - use ::godot::builtin::meta::ConvertError; - let variant = &variant; - #body - } - } - }) -} - -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(); - - if has_attr_skip(&field.attributes) { - (quote! {}, quote! { #ident: #name::default().#ident }) - } else { - let err = format!("missing expected value {string_ident}"); - ( - quote! { - let #ident = match root.get(#string_ident) { - Some(value) => value, - None => return Err(ConvertError::with_cause_value(#err, root)), - }; - }, - 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(), - if has_attr_skip(&f.attributes) { - quote! { - let #ident = <#name as Default>::default().#ident; - } - } else { - quote! { - let #ident = match root.pop_front() { - Some(value) => value.try_to::<#field_type>()?, - None => return Err(ConvertError::with_cause_value("missing expected value", root)), - }; - } - }, - ) - }); - 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 = if has_attr_skip(&fields.fields.first().unwrap().0.attributes) { - quote! { Ok(Self::default()) } - } else { - 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 let Some(v) = child.get(#variant_name_string) { - if 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 = if has_attr_skip(&field.attributes) { - quote! { - let #ident = <#field_type as Default>::default(); - } - } else { - quote! { - let #ident = variant.pop_front() - .ok_or(ConvertError::with_cause_value("missing expected value", &variant))? - .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 = if has_attr(&field.attributes, "variant", "skip") { - quote! { - let #field_name = <#field_type as Default>::default(); - } - } else { - let err = format!("missing expected value {field_name_string}"); - quote! { - let #field_name = variant.get(#field_name_string) - .ok_or(ConvertError::with_cause_value(#err, &variant))? - .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, )* - }); - } - } - } -} diff --git a/godot-macros/src/derive/derive_godot_convert.rs b/godot-macros/src/derive/derive_godot_convert.rs index b0aad029f..d54258842 100644 --- a/godot-macros/src/derive/derive_godot_convert.rs +++ b/godot-macros/src/derive/derive_godot_convert.rs @@ -9,23 +9,17 @@ use proc_macro2::TokenStream; use quote::quote; use venial::Declaration; -use crate::util::{decl_get_info, via_type, DeclInfo}; use crate::ParseResult; -pub fn derive_godot_convert(decl: Declaration) -> ParseResult { - let DeclInfo { - where_, - generic_params, - name, - .. - } = decl_get_info(&decl); +use crate::derive::data_model::GodotConvert; - let gen = generic_params.as_ref().map(|x| x.as_inline_args()); +pub fn derive_godot_convert(declaration: Declaration) -> ParseResult { + let GodotConvert { name, data } = GodotConvert::parse_declaration(declaration)?; - let via_type = via_type(&decl)?; + let via_type = data.via_type(); Ok(quote! { - impl #generic_params ::godot::builtin::meta::GodotConvert for #name #gen #where_ { + impl ::godot::builtin::meta::GodotConvert for #name { type Via = #via_type; } }) diff --git a/godot-macros/src/derive/derive_to_godot.rs b/godot-macros/src/derive/derive_to_godot.rs new file mode 100644 index 000000000..8b7fda639 --- /dev/null +++ b/godot-macros/src/derive/derive_to_godot.rs @@ -0,0 +1,86 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::quote; +use venial::Declaration; + +use crate::derive::data_model::{ConvertData, GodotConvert, ViaType}; +use crate::ParseResult; + +use super::data_model::{CStyleEnum, NewtypeField}; + +pub fn derive_to_godot(declaration: Declaration) -> ParseResult { + let GodotConvert { name, data } = GodotConvert::parse_declaration(declaration)?; + + match data { + ConvertData::NewType { field } => to_newtype(name, field), + ConvertData::Enum { + variants, + via: ViaType::GString(_), + } => to_enum_string(name, variants), + ConvertData::Enum { + variants, + via: ViaType::Int(_, int), + } => to_enum_int(name, variants, int.to_ident()), + } +} + +fn to_newtype(name: Ident, field: NewtypeField) -> ParseResult { + let field_name = field.field_name(); + let via_type = field.ty; + + Ok(quote! { + impl ::godot::builtin::meta::ToGodot for #name { + fn to_godot(&self) -> #via_type { + ::godot::builtin::meta::ToGodot::to_godot(&self.#field_name) + } + + fn into_godot(self) -> #via_type { + ::godot::builtin::meta::ToGodot::into_godot(self.#field_name) + } + } + }) +} + +fn to_enum_int(name: Ident, enum_: CStyleEnum, int: Ident) -> ParseResult { + let discriminants = enum_ + .discriminants() + .iter() + .map(|i| Literal::i64_unsuffixed(*i)) + .collect::>(); + let names = enum_.names(); + + Ok(quote! { + impl ::godot::builtin::meta::ToGodot for #name { + fn to_godot(&self) -> #int { + match self { + #( + #name::#names => #discriminants, + )* + } + } + } + }) +} + +fn to_enum_string(name: Ident, enum_: CStyleEnum) -> ParseResult { + let names = enum_.names(); + let names_str = names.iter().map(ToString::to_string).collect::>(); + + Ok(quote! { + impl ::godot::builtin::meta::ToGodot for #name { + fn to_godot(&self) -> ::godot::builtin::GString { + match self { + #( + #name::#names => #names_str.into(), + )* + } + } + } + }) +} diff --git a/godot-macros/src/derive/derive_to_variant.rs b/godot-macros/src/derive/derive_to_variant.rs deleted file mode 100644 index e92514473..000000000 --- a/godot-macros/src/derive/derive_to_variant.rs +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (c) godot-rust; Bromeon and contributors. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use venial::{Declaration, StructFields}; - -use crate::util::{decl_get_info, has_attr, DeclInfo}; -use crate::ParseResult; - -pub fn derive_to_godot(decl: Declaration) -> ParseResult { - let mut body = quote! { - let mut root = ::godot::builtin::Dictionary::new(); - }; - - let DeclInfo { - where_, - generic_params, - name, - name_string, - } = decl_get_info(&decl); - - match &decl { - Declaration::Struct(struct_) => match &struct_.fields { - StructFields::Unit => make_struct_unit(&mut body, name_string), - StructFields::Tuple(fields) => make_struct_tuple(&mut body, fields, name_string), - StructFields::Named(named_struct) => { - make_struct_named(&mut body, named_struct, name_string); - } - }, - Declaration::Enum(enum_) => { - let arms = enum_.variants.iter().map(|(enum_v, _)| { - let variant_name = enum_v.name.clone(); - let variant_name_string = enum_v.name.to_string(); - let fields = match &enum_v.contents { - StructFields::Unit => quote! {}, - StructFields::Tuple(s) => make_tuple_enum_field(s), - StructFields::Named(named) => make_named_enum_field(named), - }; - let arm_content = match &enum_v.contents { - _ if has_attr(&enum_v.attributes, "variant", "skip") => quote! { - return ::godot::builtin::dict! { - #name_string: ::godot::builtin::Variant::nil() - }.to_variant(); - }, - StructFields::Unit => quote! { #variant_name_string.to_variant() }, - - StructFields::Tuple(fields) => make_enum_tuple_arm(fields, variant_name_string), - StructFields::Named(fields) => make_enum_named_arm(fields, variant_name_string), - }; - quote! { - Self::#variant_name #fields => { - #arm_content - } - } - }); - - body = quote! { - #body - let content = match core::clone::Clone::clone(self) { - #( - #arms - )* - }; - root.insert(#name_string, content); - }; - } - - // decl_get_info() above ensured that no other cases are possible. - _ => unreachable!(), - }; - - body = quote! { - #body - root.to_variant() - }; - - let gen = generic_params.as_ref().map(|x| x.as_inline_args()); - - // We need to allow unreachable code for uninhabited enums, because it uses match self {}. - // This is safe, since we can't ever have a value to call to_variant on it anyway. - let allow_unreachable = if matches!(&decl, Declaration::Enum(e) if e.variants.is_empty()) { - quote! { - #[allow(unreachable_code)] - } - } else { - TokenStream::new() - }; - - Ok(quote! { - impl #generic_params ::godot::builtin::meta::ToGodot for #name #gen #where_ { - #allow_unreachable - fn to_godot(&self) -> ::godot::builtin::Variant { - #body - } - } - }) -} - -fn make_named_enum_field(named: &venial::NamedStructFields) -> TokenStream { - let fields = named.fields.iter().map(|(field, _)| { - let field_name = field.name.to_token_stream(); - if has_attr(&field.attributes, "variant", "skip") { - quote! { #field_name : _ } - } else { - field_name - } - }); - quote! { { #(#fields ,)* } } -} - -fn make_tuple_enum_field(s: &venial::TupleStructFields) -> TokenStream { - let fields = s.fields.iter().enumerate().map(|(k, (f, _))| { - if has_attr(&f.attributes, "variant", "skip") { - quote! { _ } - } else { - format_ident!("__{}", k).to_token_stream() - } - }); - - quote! { - (#(#fields ,)*) - } -} - -fn make_enum_named_arm( - fields: &venial::NamedStructFields, - variant_name_string: String, -) -> TokenStream { - let fields = fields - .fields - .iter() - .filter(|(f, _)| !has_attr(&f.attributes, "variant", "skip")) - .map(|(field, _)| (field.name.clone(), field.name.to_string())) - .map(|(ident, ident_string)| { - quote! { - root.insert(#ident_string,#ident.to_variant()); - } - }); - - quote! { - let mut root = ::godot::builtin::Dictionary::new(); - #( - #fields - )* - ::godot::builtin::dict! { #variant_name_string: root }.to_variant() - } -} - -fn make_enum_tuple_arm( - fields: &venial::TupleStructFields, - variant_name_string: String, -) -> TokenStream { - if fields.fields.len() == 1 { - let res = if has_attr( - &fields.fields.first().unwrap().0.attributes, - "variant", - "skip", - ) { - quote! { godot::builtin::Variant::nil() } - } else { - quote! { __0 } - }; - return quote! { godot::builtin::dict! { #variant_name_string : #res }.to_variant() }; - } - let fields = fields - .fields - .iter() - .enumerate() - .filter(|(_, (f, _))| !has_attr(&f.attributes, "variant", "skip")) - .map(|(k, _)| format_ident!("__{}", k)) - .map(|ident| { - quote! { - root.push(#ident.to_variant()); - } - }); - quote! { - let mut root = godot::builtin::VariantArray::new(); - #( - #fields - - )* - godot::builtin::dict!{ #variant_name_string: root }.to_variant() - } -} - -fn make_struct_named( - body: &mut TokenStream, - fields: &venial::NamedStructFields, - string_ident: String, -) { - let fields = fields - .fields - .items() - .filter(|f| !has_attr(&f.attributes, "variant", "skip")) - .map(|nf| { - let field_name = nf.name.clone(); - let field_name_string = nf.name.to_string(); - - quote! { - fields.insert(#field_name_string, self.#field_name.to_variant()); - } - }); - - *body = quote! { - #body - let mut fields = godot::builtin::Dictionary::new(); - #( - #fields - )* - root.insert(#string_ident, fields.to_variant()); - }; -} - -fn make_struct_tuple( - body: &mut TokenStream, - fields: &venial::TupleStructFields, - string_ident: String, -) { - if fields.fields.len() == 1 - && !has_attr( - &fields.fields.first().unwrap().0.attributes, - "variant", - "skip", - ) - { - *body = quote! { - #body - root.insert(#string_ident, self.0.to_variant()); - }; - - return; - } - let fields = fields - .fields - .iter() - .filter(|(f, _)| !has_attr(&f.attributes, "variant", "skip")) - .enumerate() - .map(|(k, _)| proc_macro2::Literal::usize_unsuffixed(k)) - .map(|ident| { - quote! { - fields.push(self.#ident.to_variant()); - } - }); - - *body = quote! { - #body - let mut fields = godot::builtin::VariantArray::new(); - #( - #fields - )* - root.insert(#string_ident, fields.to_variant()); - }; -} - -fn make_struct_unit(body: &mut TokenStream, string_ident: String) { - *body = quote! { - #body - root.insert(#string_ident, godot::builtin::Variant::nil()); - } -} diff --git a/godot-macros/src/derive/derive_var.rs b/godot-macros/src/derive/derive_var.rs index 469954beb..c4bc0408e 100644 --- a/godot-macros/src/derive/derive_var.rs +++ b/godot-macros/src/derive/derive_var.rs @@ -5,104 +5,62 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::TokenStream; use quote::quote; -use venial::{Declaration, StructFields}; +use venial::Declaration; -use crate::util::{bail, decl_get_info, via_type, DeclInfo}; use crate::ParseResult; -pub fn derive_var(decl: Declaration) -> ParseResult { - let DeclInfo { - name, name_string, .. - } = decl_get_info(&decl); +use super::data_model::GodotConvert; - let body_get; - let body_set; - let via_type = via_type(&decl)?; +pub fn derive_var(declaration: Declaration) -> ParseResult { + let convert = GodotConvert::parse_declaration(declaration)?; - let enum_ = match decl { - Declaration::Enum(e) => e, - Declaration::Struct(s) => { - return bail!(s.tk_struct, "Property can only be derived on enums for now") - } - Declaration::Union(u) => { - return bail!(u.tk_union, "Property can only be derived on enums for now") - } - _ => unreachable!(), - }; + let property_hint_impl = create_property_hint_impl(&convert); - if enum_.variants.is_empty() { - return bail!( - enum_.name, - "In order to derive Property, enums must have at least one variant" - ); - } else { - let mut matches_get = quote! {}; - let mut matches_set = quote! {}; + let name = convert.name; - for (enum_v, _) in enum_.variants.inner.iter() { - let v_name = enum_v.name.clone(); - let v_disc = if let Some(c) = enum_v.value.clone() { - c.value - } else { - return bail!( - v_name, - "Property can only be derived on enums with explicit discriminants in all their variants" - ); - }; + Ok(quote! { + impl ::godot::register::property::Var for #name { + fn get_property(&self) -> ::Via { + ::godot::builtin::meta::ToGodot::to_godot(self) + } - let match_content_get; - let match_content_set; - match &enum_v.contents { - StructFields::Unit => { - match_content_get = quote! { - Self::#v_name => #v_disc, - }; - match_content_set = quote! { - #v_disc => Self::#v_name, - }; - } - _ => { - return bail!( - v_name, - "Property can only be derived on enums with only unit variants for now" - ) - } - }; - matches_get = quote! { - #matches_get - #match_content_get - }; - matches_set = quote! { - #matches_set - #match_content_set - }; - } - body_get = quote! { - match &self { - #matches_get + fn set_property(&mut self, value: ::Via) { + *self = ::godot::builtin::meta::FromGodot::from_godot(value); } - }; - body_set = quote! { - *self = match value { - #matches_set - _ => panic!("Incorrect conversion from {} to {}", stringify!(#via_type), #name_string), + + fn property_hint() -> ::godot::register::property::PropertyHintInfo { + #property_hint_impl } - }; - } - let out = quote! { - #[allow(unused_parens)] - impl godot::register::property::Var for #name { - fn get_property(&self) -> #via_type { - #body_get + } + }) +} + +fn create_property_hint_impl(convert: &GodotConvert) -> TokenStream { + use super::data_model::ConvertData as Data; + use super::data_model::ViaType; + + match &convert.data { + Data::NewType { field } => { + let ty = &field.ty; + quote! { + <#ty as ::godot::register::property::Var>::property_hint() } + } + Data::Enum { variants, via } => { + let hint_string = match via { + ViaType::GString(_) => variants.to_string_hint(), + ViaType::Int(_, _) => variants.to_int_hint(), + }; - fn set_property(&mut self, value: #via_type) { - #body_set + quote! { + ::godot::register::property::PropertyHintInfo { + hint: ::godot::engine::global::PropertyHint::ENUM, + hint_string: #hint_string.into(), + } } } - }; - Ok(out) + } } diff --git a/godot-macros/src/derive/mod.rs b/godot-macros/src/derive/mod.rs index f20ff282c..6641df0a3 100644 --- a/godot-macros/src/derive/mod.rs +++ b/godot-macros/src/derive/mod.rs @@ -7,14 +7,15 @@ //! Derive macros on types outside of classes. +mod data_model; mod derive_export; -mod derive_from_variant; +mod derive_from_godot; mod derive_godot_convert; -mod derive_to_variant; +mod derive_to_godot; mod derive_var; pub(crate) use derive_export::*; -pub(crate) use derive_from_variant::*; +pub(crate) use derive_from_godot::*; pub(crate) use derive_godot_convert::*; -pub(crate) use derive_to_variant::*; +pub(crate) use derive_to_godot::*; pub(crate) use derive_var::*; diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 9f5128053..f96fa0500 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -527,7 +527,7 @@ pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream { } /// Derive macro for [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) on structs (required by [`ToGodot`] and [`FromGodot`]). -#[proc_macro_derive(GodotConvert)] +#[proc_macro_derive(GodotConvert, attributes(godot))] pub fn derive_godot_convert(input: TokenStream) -> TokenStream { translate(input, derive::derive_godot_convert) } @@ -560,7 +560,7 @@ pub fn derive_godot_convert(input: TokenStream) -> TokenStream { /// ``` /// /// You can use the `#[skip]` attribute to ignore a field from being converted to `ToGodot`. -#[proc_macro_derive(ToGodot, attributes(variant))] +#[proc_macro_derive(ToGodot, attributes(godot))] pub fn derive_to_godot(input: TokenStream) -> TokenStream { translate(input, derive::derive_to_godot) } @@ -594,7 +594,7 @@ pub fn derive_to_godot(input: TokenStream) -> TokenStream { /// /// You can use the skip attribute to ignore a field from the provided variant and use `Default::default()` /// to get it instead. -#[proc_macro_derive(FromGodot, attributes(variant))] +#[proc_macro_derive(FromGodot, attributes(godot))] pub fn derive_from_godot(input: TokenStream) -> TokenStream { translate(input, derive::derive_from_godot) } @@ -637,7 +637,7 @@ pub fn derive_from_godot(input: TokenStream) -> TokenStream { /// assert_eq!(class.foo, MyEnum::A); /// } /// ``` -#[proc_macro_derive(Var)] +#[proc_macro_derive(Var, attributes(godot))] pub fn derive_property(input: TokenStream) -> TokenStream { translate(input, derive::derive_var) } @@ -645,7 +645,7 @@ pub fn derive_property(input: TokenStream) -> TokenStream { /// Derive macro for [`Export`](../register/property/trait.Export.html) on enums. /// /// Currently has some tight requirements which are expected to be softened as implementation expands, see requirements for [`Var`]. -#[proc_macro_derive(Export)] +#[proc_macro_derive(Export, attributes(godot))] pub fn derive_export(input: TokenStream) -> TokenStream { translate(input, derive::derive_export) } diff --git a/godot-macros/src/util/mod.rs b/godot-macros/src/util/mod.rs index 3f89f6744..faa6b1157 100644 --- a/godot-macros/src/util/mod.rs +++ b/godot-macros/src/util/mod.rs @@ -7,6 +7,8 @@ // Note: some code duplication with codegen crate +use std::collections::HashMap; + use crate::ParseResult; use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree}; use quote::spanned::Spanned; @@ -288,74 +290,3 @@ pub fn make_virtual_tool_check() -> TokenStream { } } } - -pub enum ViaType { - Struct, - EnumWithRepr { int_ty: Ident }, - Enum, -} - -impl ToTokens for ViaType { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - ViaType::Struct | ViaType::Enum => { - quote! { ::godot::builtin::Variant }.to_tokens(tokens) - } - ViaType::EnumWithRepr { int_ty } => int_ty.to_tokens(tokens), - } - } -} - -pub fn via_type(declaration: &venial::Declaration) -> ParseResult { - use venial::Declaration; - - match declaration { - Declaration::Enum(enum_) => enum_repr(enum_), - Declaration::Struct(_) => Ok(ViaType::Struct), - other => bail!( - other, - "cannot get via type for {:?}, only structs and enums are supported currently", - other.name() - ), - } -} - -pub fn enum_repr(enum_: &venial::Enum) -> ParseResult { - let Some(repr) = enum_ - .attributes - .iter() - .find(|attr| attr.get_single_path_segment() == Some(&ident("repr"))) - else { - return Ok(ViaType::Enum); - }; - - let venial::AttributeValue::Group(_, repr_value) = &repr.value else { - // `repr` is always going to look like `#[repr(..)]` - unreachable!() - }; - - let Some(repr_type) = repr_value.first() else { - // `#[repr()]` is just a warning apparently, so we're gonna give an error if that's provided. - return bail!(&repr.value, "expected non-empty `repr` list"); - }; - - let TokenTree::Ident(repr_type) = &repr_type else { - // all valid non-empty `#[repr(..)]` will have an ident as its first element. - unreachable!(); - }; - - if !matches!( - repr_type.to_string().as_str(), - "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" - ) { - return bail!( - &repr_type, - "enum with repr #[repr({})] cannot implement `GodotConvert`, repr must be one of: i8, i16, i32, i64, u8, u16, u32", - repr_type.to_string(), - ); - } - - Ok(ViaType::EnumWithRepr { - int_ty: repr_type.clone(), - }) -} diff --git a/itest/rust/src/object_tests/property_test.rs b/itest/rust/src/object_tests/property_test.rs index 814a113bb..ad7b24436 100644 --- a/itest/rust/src/object_tests/property_test.rs +++ b/itest/rust/src/object_tests/property_test.rs @@ -11,7 +11,7 @@ use godot::engine::global::{PropertyHint, PropertyUsageFlags}; use godot::engine::{INode, IRefCounted, Node, Object, RefCounted, Texture}; use godot::obj::{Base, EngineBitfield, EngineEnum, Gd, NewAlloc, NewGd}; use godot::register::property::{Export, PropertyHintInfo, Var}; -use godot::register::{godot_api, Export, GodotClass, GodotConvert, Var}; +use godot::register::{godot_api, Export, FromGodot, GodotClass, GodotConvert, ToGodot, Var}; use godot::test::itest; // No tests currently, tests using these classes are in Godot scripts. @@ -303,8 +303,8 @@ struct CheckAllExports { color_no_alpha: Color, } -#[derive(GodotConvert, Var, Export, Eq, PartialEq, Debug)] -#[repr(i64)] +#[derive(GodotConvert, ToGodot, FromGodot, Var, Export, Eq, PartialEq, Debug)] +#[godot(via = i64)] pub enum TestEnum { A = 0, B = 1, diff --git a/itest/rust/src/register_tests/derive_variant_test.rs b/itest/rust/src/register_tests/derive_variant_test.rs index 5ba379d07..1921c7596 100644 --- a/itest/rust/src/register_tests/derive_variant_test.rs +++ b/itest/rust/src/register_tests/derive_variant_test.rs @@ -8,7 +8,7 @@ use std::fmt::Debug; use godot::builtin::meta::{FromGodot, ToGodot}; -use godot::builtin::{dict, varray, Variant}; +use godot::builtin::{dict, varray, GString, Variant, Vector2}; use godot::register::{FromGodot, GodotConvert, ToGodot}; use crate::common::roundtrip; @@ -18,250 +18,93 @@ use crate::framework::itest; // General FromGodot/ToGodot derive tests #[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -struct StructUnit; +#[godot(transparent)] +struct TupleNewtype(GString); #[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -struct StructNewType(String); - -#[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -struct StructTuple(String, i32); - -#[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -struct StructNamed { - field1: String, - field2: i32, +#[godot(transparent)] +struct NamedNewtype { + field1: Vector2, } -#[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -struct StructGenWhere(T) -where - T: ToGodot + FromGodot; - -trait Bound {} - -#[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -struct StructGenBound(T); - -#[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] -enum Uninhabited {} - #[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] -enum Enum { - Unit, - OneTuple(i32), - Named { data: String }, - Tuple(String, i32), -} - -#[itest] -fn unit_struct() { - roundtrip(StructUnit); - roundtrip(dict! { "StructUnit": godot::builtin::Variant::nil() }); -} - -#[itest] -fn new_type_struct() { - roundtrip(StructNewType(String::from("five"))); - roundtrip(dict! { "StructNewType" : "five" }) -} - -#[itest] -fn tuple_struct() { - roundtrip(StructTuple(String::from("one"), 2)); - roundtrip(dict! { - "StructTuple": varray!["one", 2] - }); -} - -#[itest] -fn named_struct() { - roundtrip(StructNamed { - field1: String::from("four"), - field2: 5, - }); - roundtrip(dict! { - "StructNamed": dict! { "field1": "four", "field2": 5 } - }); -} - -#[itest] -fn generics() { - roundtrip(StructGenWhere(String::from("4"))); - roundtrip(dict! { "StructGenWhere": "4" }); +#[godot(via = GString)] +enum EnumStringy { + A, + B, + C = 10, + D = 50, } -impl Bound for String {} - -#[itest] -fn generics_bound() { - roundtrip(StructGenBound(String::from("4"))); - roundtrip(dict! { "StructGenBound": "4" }); +#[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] +#[godot(via = i64)] +enum EnumInty { + A = 10, + B, + C, + D = 1, + E, } #[itest] -fn enum_unit() { - roundtrip(Enum::Unit); - roundtrip(dict! { "Enum": "Unit" }); +fn newtype_tuple_struct() { + roundtrip(TupleNewtype("hello!".into())); } #[itest] -fn enum_one_tuple() { - roundtrip(Enum::OneTuple(4)); - roundtrip(dict! { - "Enum": dict! { "OneTuple" : 4 } +fn newtype_named_struct() { + roundtrip(NamedNewtype { + field1: Vector2::new(10.0, 25.0), }); } #[itest] -fn enum_tuple() { - roundtrip(Enum::Tuple(String::from("four"), 5)); - roundtrip(dict! { "Enum": dict! { "Tuple" : varray!["four", 5] } }); +fn enum_stringy() { + roundtrip(EnumStringy::A); + assert_eq!(EnumStringy::A.to_godot(), "A".into()); + roundtrip(EnumStringy::B); + assert_eq!(EnumStringy::B.to_godot(), "B".into()); + roundtrip(EnumStringy::C); + assert_eq!(EnumStringy::C.to_godot(), "C".into()); + roundtrip(EnumStringy::D); + assert_eq!(EnumStringy::D.to_godot(), "D".into()); } #[itest] -fn enum_named() { - roundtrip(Enum::Named { - data: String::from("data"), - }); - roundtrip(dict! { - "Enum": dict!{ "Named": dict!{ "data": "data" } } - }); -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- -// Skipping of enums - -macro_rules! roundtrip_with_skip { - ($name_to:ident, $name_from:ident, $value:expr, $to_var:expr, $from_var:expr) => { - #[itest] - fn $name_to() { - let s = $value; - assert_eq!(s.to_variant(), $to_var.to_variant(),) +fn enum_inty() { + roundtrip(EnumInty::A); + assert_eq!(EnumInty::A.to_godot(), 10); + roundtrip(EnumInty::B); + assert_eq!(EnumInty::B.to_godot(), 0); + roundtrip(EnumInty::C); + assert_eq!(EnumInty::C.to_godot(), 2); + roundtrip(EnumInty::D); + assert_eq!(EnumInty::D.to_godot(), 1); + roundtrip(EnumInty::E); + assert_eq!(EnumInty::E.to_godot(), 3); +} + +macro_rules! test_inty { + ($T:ident, $test_name:ident, $class_name:ident) => { + #[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] + #[godot(via = $T)] + enum $class_name { + A, + B, } #[itest] - fn $name_from() { - assert_eq!(EnumWithSkip::from_variant(&$to_var.to_variant()), $from_var); + fn $test_name() { + roundtrip($class_name::A); + roundtrip($class_name::B); } }; } -#[derive(ToGodot, FromGodot, GodotConvert, Default, Clone, PartialEq, Debug)] -enum EnumWithSkip { - #[variant(skip)] - Skipped(String), - NewType(#[variant(skip)] String), - PartSkippedTuple(#[variant(skip)] String, String), - PartSkippedNamed { - #[variant(skip)] - skipped_data: String, - data: String, - }, - #[default] - Default, -} - -roundtrip_with_skip!( - skipped_to_variant, - skipped_from_variant, - EnumWithSkip::Skipped("one".to_string()), - dict! { "EnumWithSkip" : Variant::nil() }, - EnumWithSkip::default() -); - -roundtrip_with_skip!( - skipped_newtype_to_variant, - skipped_newtype_from_variant, - EnumWithSkip::NewType("whatever".to_string()), - dict! { "EnumWithSkip" : dict!{ "NewType" : Variant::nil() } }, - EnumWithSkip::NewType(String::default()) -); - -roundtrip_with_skip!( - skipped_tuple_to_variant, - skipped_tuple_from_variant, - EnumWithSkip::PartSkippedTuple("skipped".to_string(), "three".to_string()), - dict! { - "EnumWithSkip": dict!{ - "PartSkippedTuple" : varray!["three"] - } - }, - EnumWithSkip::PartSkippedTuple(String::default(), "three".to_string()) -); - -roundtrip_with_skip!( - named_skipped_to_variant, - named_skipped_from_variant, - EnumWithSkip::PartSkippedNamed { - skipped_data: "four".to_string(), - data: "five".to_string(), - }, - dict! { - "EnumWithSkip": dict!{ - "PartSkippedNamed" : dict! { "data" : "five" } - } - }, - EnumWithSkip::PartSkippedNamed { - data: "five".to_string(), - skipped_data: String::default() - } -); - -// ---------------------------------------------------------------------------------------------------------------------------------------------- -// Skipping of structs - -#[derive(ToGodot, FromGodot, GodotConvert, Default, PartialEq, Debug)] -struct NewTypeStructWithSkip(#[variant(skip)] String); - -#[derive(ToGodot, FromGodot, GodotConvert, Default, PartialEq, Debug)] -struct StructWithSkip { - #[variant(skip)] - skipped_field: String, - field: String, -} - -#[itest] -fn new_type_to_variant() { - assert_eq!( - NewTypeStructWithSkip("four".to_string()).to_variant(), - dict! {"NewTypeStructWithSkip" : varray![] }.to_variant() - ); -} - -#[itest] -fn new_type_from_variant() { - let s = NewTypeStructWithSkip("four".to_string()); - assert_eq!( - NewTypeStructWithSkip::from_variant(&s.to_variant()), - NewTypeStructWithSkip::default() - ) -} - -#[itest] -fn struct_with_skip_to_variant() { - assert_eq!( - StructWithSkip { - skipped_field: "four".to_string(), - field: "seven".to_string(), - } - .to_variant(), - dict! { "StructWithSkip" : dict! { "field" : "seven" } }.to_variant() - ); -} - -#[itest] -fn struct_with_skip_from_variant() { - assert_eq!( - StructWithSkip { - field: "seven".to_string(), - ..Default::default() - }, - StructWithSkip::from_variant( - &StructWithSkip { - skipped_field: "four".to_string(), - field: "seven".to_string(), - } - .to_variant() - ) - ); -} +test_inty!(i8, test_enum_i8, EnumI8); +test_inty!(i16, test_enum_16, EnumI16); +test_inty!(i32, test_enum_i32, EnumI32); +test_inty!(i64, test_enum_i64, EnumI64); +test_inty!(u8, test_enum_u8, EnumU8); +test_inty!(u16, test_enum_u16, EnumU16); +test_inty!(u32, test_enum_u32, EnumU32); From 4b445c692aba8d9693936c29ac55ae1abbef9ec3 Mon Sep 17 00:00:00 2001 From: Lili Zoey Zyli Date: Wed, 7 Feb 2024 19:45:47 +0100 Subject: [PATCH 2/2] Make a `data_models` module for all the data models in `derive` Document things properly Other cleanup --- godot-macros/src/derive/data_model.rs | 348 ------------------ .../src/derive/data_models/c_style_enum.rs | 150 ++++++++ .../src/derive/data_models/godot_attribute.rs | 90 +++++ .../src/derive/data_models/godot_convert.rs | 117 ++++++ godot-macros/src/derive/data_models/mod.rs | 16 + .../src/derive/data_models/newtype.rs | 70 ++++ godot-macros/src/derive/derive_export.rs | 7 +- godot-macros/src/derive/derive_from_godot.rs | 63 ++-- .../src/derive/derive_godot_convert.rs | 18 +- godot-macros/src/derive/derive_to_godot.rs | 61 +-- godot-macros/src/derive/derive_var.rs | 21 +- godot-macros/src/derive/mod.rs | 2 +- godot-macros/src/lib.rs | 200 +++++----- godot-macros/src/util/kv_parser.rs | 7 - godot-macros/src/util/mod.rs | 39 +- godot/src/lib.rs | 2 +- godot/src/prelude.rs | 2 +- itest/rust/src/object_tests/property_test.rs | 5 +- .../src/register_tests/derive_variant_test.rs | 34 +- 19 files changed, 675 insertions(+), 577 deletions(-) delete mode 100644 godot-macros/src/derive/data_model.rs create mode 100644 godot-macros/src/derive/data_models/c_style_enum.rs create mode 100644 godot-macros/src/derive/data_models/godot_attribute.rs create mode 100644 godot-macros/src/derive/data_models/godot_convert.rs create mode 100644 godot-macros/src/derive/data_models/mod.rs create mode 100644 godot-macros/src/derive/data_models/newtype.rs diff --git a/godot-macros/src/derive/data_model.rs b/godot-macros/src/derive/data_model.rs deleted file mode 100644 index 396b01342..000000000 --- a/godot-macros/src/derive/data_model.rs +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (c) godot-rust; Bromeon and contributors. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -use std::collections::HashMap; - -use proc_macro2::{Ident, Span, TokenStream, TokenTree}; -use quote::{quote, ToTokens}; -use venial::{Declaration, TyExpr}; - -use crate::{ - util::{bail, decl_get_info, ident, DeclInfo, KvParser}, - ParseResult, -}; - -pub struct GodotConvert { - pub name: Ident, - pub data: ConvertData, -} - -impl GodotConvert { - pub fn parse_declaration(declaration: Declaration) -> ParseResult { - let DeclInfo { name, where_, generic_params, .. } = decl_get_info(&declaration); - - if let Some(where_) = where_ { - return bail!(where_, "where clauses are currently not supported for `GodotConvert`") - } - - if let Some(generic_params) = generic_params { - return bail!(generic_params, "generics are currently not supported for `GodotConvert`") - } - - let data = ConvertData::parse_declaration(&declaration)?; - - Ok(Self { name, data }) - } - - pub fn name(&self) -> &Ident { - &self.name - } - - pub fn data(&self) -> &ConvertData { - &self.data - } -} - -pub enum ConvertData { - NewType { field: NewtypeField }, - Enum { variants: CStyleEnum, via: ViaType }, -} - -impl ConvertData { - pub fn parse_declaration(declaration: &Declaration) -> ParseResult { - let attribute = GodotAttribute::parse_attribute(declaration)?; - - match declaration { - Declaration::Struct(struct_) => { - if let GodotAttribute::Via { via_type: ty } = attribute { - return bail!(ty.span(), "`GodotConvert` on structs only works with `#[godot(transparent)]` currently"); - } - - Ok(Self::NewType { - field: NewtypeField::parse_struct(struct_)?, - }) - } - Declaration::Enum(enum_) => { - let via_type = match attribute { - GodotAttribute::Transparent { span } => { - return bail!( - span, - "`GodotConvert` on enums requires `#[godot(via = ..)]` currently" - ) - } - GodotAttribute::Via { via_type: ty } => ty, - }; - - Ok(Self::Enum { - variants: CStyleEnum::parse_enum(enum_)?, - via: via_type, - }) - } - _ => bail!( - declaration, - "`GodotConvert` only supports structs and enums currently" - ), - } - } - - pub fn via_type(&self) -> TokenStream { - match self { - ConvertData::NewType { field } => field.ty.to_token_stream(), - ConvertData::Enum { variants, via } => via.to_token_stream(), - } - } -} - -pub enum GodotAttribute { - Transparent { span: Span }, - Via { via_type: ViaType }, -} -impl GodotAttribute { - pub fn parse_attribute(declaration: &Declaration) -> ParseResult { - let mut parser = KvParser::parse_required(declaration.attributes(), "godot", declaration)?; - - let span = parser.span(); - - if parser.handle_alone("transparent")? { - return Ok(Self::Transparent { span }); - } - - let via_type = parser.handle_ident_required("via")?; - - let span = via_type.span(); - - let via_type = match via_type.to_string().as_str() { - "GString" => ViaType::GString(span), - "i8" => ViaType::Int(span, ViaInt::I8), - "i16" => ViaType::Int(span, ViaInt::I16), - "i32" => ViaType::Int(span, ViaInt::I32), - "i64" => ViaType::Int(span, ViaInt::I64), - "u8" => ViaType::Int(span, ViaInt::U8), - "u16" => ViaType::Int(span, ViaInt::U16), - "u32" => ViaType::Int(span, ViaInt::U32), - other => return bail!(via_type, "Via type `{}` is not supported, expected one of: GString, i8, i16, i32, i64, u8, u16, u32", other) - }; - - Ok(Self::Via { via_type }) - } - - pub fn span(&self) -> Span { - match self { - GodotAttribute::Transparent { span } => span.clone(), - GodotAttribute::Via { via_type } => via_type.span(), - } - } -} - -pub enum ViaType { - GString(Span), - Int(Span, ViaInt), -} - -impl ViaType { - fn span(&self) -> Span { - match self { - ViaType::GString(span) => span.clone(), - ViaType::Int(span, _) => span.clone(), - } - } - - fn to_token_stream(&self) -> TokenStream { - match self { - ViaType::GString(_) => quote! { ::godot::builtin::GString }, - ViaType::Int(_, int) => { - let id = int.to_ident(); - quote! { #id } - }, - } - } -} - -pub enum ViaInt { - I8, - I16, - I32, - I64, - U8, - U16, - U32, -} - -impl ViaInt { - pub fn to_ident(&self) -> Ident { - match self { - ViaInt::I8 => ident("i8" ), - ViaInt::I16 => ident("i16" ), - ViaInt::I32 => ident("i32" ), - ViaInt::I64 => ident("i64" ), - ViaInt::U8 => ident("u8" ), - ViaInt::U16 => ident("u16" ), - ViaInt::U32 => ident("u32" ), - } - } -} - -pub struct NewtypeField { - // If none, then it's the first field of a tuple-struct. - pub name: Option, - pub ty: TyExpr, -} - -impl NewtypeField { - pub fn parse_struct(struct_: &venial::Struct) -> ParseResult { - match &struct_.fields { - venial::StructFields::Unit => return bail!(&struct_.fields, "GodotConvert expects a struct with a single field, unit structs are currently not supported"), - venial::StructFields::Tuple(fields) => { - if fields.fields.len() != 1 { - return bail!(&fields.fields, "GodotConvert expects a struct with a single field, not {} fields", fields.fields.len()) - } - - let (field, _) = fields.fields[0].clone(); - - Ok(NewtypeField { name: None, ty: field.ty }) - }, - venial::StructFields::Named(fields) => { - if fields.fields.len() != 1 { - return bail!(&fields.fields, "GodotConvert expects a struct with a single field, not {} fields", fields.fields.len()) - } - - let (field, _) = fields.fields[0].clone(); - - Ok(NewtypeField { name: Some(field.name), ty: field.ty }) - }, - } - } - - pub fn field_name(&self) -> TokenStream { - match &self.name { - Some(name) => quote! { #name }, - None => quote! { 0 }, - } - } - - - -} - -#[derive(Debug, Clone)] -pub struct CStyleEnumVariant { - pub name: Ident, - pub discriminant: Option, -} - -impl CStyleEnumVariant { - pub fn parse_enum_variant(enum_variant: &venial::EnumVariant) -> ParseResult { - match enum_variant.contents { - venial::StructFields::Unit => {} - _ => { - return bail!( - &enum_variant.contents, - "GodotConvert only supports c-style enums (enums without fields)" - ) - } - } - - Ok(Self { - name: enum_variant.name.clone(), - discriminant: enum_variant.value.as_ref().map(|val| &val.value).cloned(), - }) - } -} - -#[derive(Debug, Clone)] -pub struct CStyleEnum { - names: Vec, - discriminants: Vec, -} - -impl CStyleEnum { - pub fn parse_enum(enum_: &venial::Enum) -> ParseResult { - let variants = enum_ - .variants - .items() - .map(|enum_variant| CStyleEnumVariant::parse_enum_variant(enum_variant)) - .collect::>>()?; - - let mut key_values = Self::to_key_value_assignment(variants)? - .into_iter() - .collect::>(); - - key_values.sort_by_key(|kv| kv.0); - - let mut names = Vec::new(); - let mut discriminants = Vec::new(); - - for (key, value) in key_values.into_iter() { - names.push(value); - discriminants.push(key); - } - - Ok(Self { - names, - discriminants, - }) - } - - fn to_key_value_assignment( - variants: Vec, - ) -> ParseResult> { - let mut unassigned_names = std::collections::VecDeque::new(); - let mut discriminants = HashMap::new(); - - for variant in variants.iter() { - if let Some(disc) = &variant.discriminant { - let Ok(disc_int) = disc.to_string().parse::() else { - return bail!(disc, "expected integer literal discriminant"); - }; - - discriminants.insert(disc_int, variant.name.clone()); - } else { - unassigned_names.push_back(variant.name.clone()); - } - } - - for i in 0.. { - if unassigned_names.is_empty() { - break; - } - - if discriminants.contains_key(&i) { - continue; - } - - let name = unassigned_names.pop_front().unwrap(); - - discriminants.insert(i, name); - } - Ok(discriminants) - } - - pub fn discriminants(&self) -> &[i64] { - &self.discriminants - } - - pub fn names(&self) -> &[Ident] { - &self.names - } - - pub fn to_int_hint(&self) -> String { - self.names - .iter() - .zip(self.discriminants.iter()) - .map(|(name, discrim)| format!("{name}:{discrim}")) - .collect::>() - .join(",") - } - - pub fn to_string_hint(&self) -> String { - self.names - .iter() - .map(ToString::to_string) - .collect::>() - .join(",") - } -} diff --git a/godot-macros/src/derive/data_models/c_style_enum.rs b/godot-macros/src/derive/data_models/c_style_enum.rs new file mode 100644 index 000000000..8e1947d1e --- /dev/null +++ b/godot-macros/src/derive/data_models/c_style_enum.rs @@ -0,0 +1,150 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::{Ident, Literal, Span, TokenTree}; + +use crate::util::{bail, error}; +use crate::ParseResult; + +/// Stores info from c-style enums for use in deriving `GodotConvert` and other related traits. +#[derive(Clone, Debug)] +pub struct CStyleEnum { + /// The names of each variant. + enumerator_names: Vec, + /// The discriminants of each variant, both explicit and implicit. + enumerator_ords: Vec, +} + +impl CStyleEnum { + /// Parses the enum. + /// + /// Ensures all the variants are unit variants, and that any explicit discriminants are integer literals. + pub fn parse_enum(enum_: &venial::Enum) -> ParseResult { + let variants = enum_ + .variants + .items() + .map(CStyleEnumerator::parse_enum_variant) + .collect::>>()?; + + let (names, discriminants) = Self::create_discriminant_mapping(variants)?; + + Ok(Self { + enumerator_names: names, + enumerator_ords: discriminants, + }) + } + + fn create_discriminant_mapping( + enumerators: Vec, + ) -> ParseResult<(Vec, Vec)> { + // See here for how implicit discriminants are decided + // https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants + let mut names = Vec::new(); + let mut discriminants = Vec::new(); + + let mut last_discriminant = None; + for enumerator in enumerators.into_iter() { + let discriminant_span = enumerator.discriminant_span(); + + let discriminant = match enumerator.discriminant_as_i64()? { + Some(discriminant) => discriminant, + None => last_discriminant.unwrap_or(0) + 1, + }; + last_discriminant = Some(discriminant); + + let mut discriminant = Literal::i64_unsuffixed(discriminant); + discriminant.set_span(discriminant_span); + + names.push(enumerator.name); + discriminants.push(discriminant) + } + + Ok((names, discriminants)) + } + + /// Returns the names of the variants, in order of the variants. + pub fn names(&self) -> &[Ident] { + &self.enumerator_names + } + + /// Returns the discriminants of each variant, in order of the variants. + pub fn discriminants(&self) -> &[Literal] { + &self.enumerator_ords + } + + /// Return a hint string for use with `PropertyHint::ENUM` where each variant has an explicit integer hint. + pub fn to_int_hint(&self) -> String { + self.enumerator_names + .iter() + .zip(self.enumerator_ords.iter()) + .map(|(name, discrim)| format!("{name}:{discrim}")) + .collect::>() + .join(",") + } + + /// Return a hint string for use with `PropertyHint::ENUM` where the variants are just kept as strings. + pub fn to_string_hint(&self) -> String { + self.enumerator_names + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + } +} + +/// Each variant in a c-style enum. +#[derive(Clone, Debug)] +pub struct CStyleEnumerator { + /// The name of the variant. + name: Ident, + /// The explicit discriminant of the variant, `None` means there was no explicit discriminant. + discriminant: Option, +} + +impl CStyleEnumerator { + /// Parse an enum variant, erroring if it isn't a unit variant. + fn parse_enum_variant(enum_variant: &venial::EnumVariant) -> ParseResult { + match enum_variant.contents { + venial::StructFields::Unit => {} + _ => { + return bail!( + &enum_variant.contents, + "GodotConvert only supports c-style enums" + ) + } + } + + Ok(Self { + name: enum_variant.name.clone(), + discriminant: enum_variant.value.as_ref().map(|val| &val.value).cloned(), + }) + } + + /// Returns the discriminant parsed as an i64 literal. + fn discriminant_as_i64(&self) -> ParseResult> { + let Some(discriminant) = self.discriminant.as_ref() else { + return Ok(None); + }; + + let int = discriminant + .to_string() + .parse::() + .map_err(|_| error!(discriminant, "expected i64 literal"))?; + + Ok(Some(int)) + } + + /// Returns a span suitable for the discriminant of the variant. + /// + /// If there was no explicit discriminant, this will use the span of the name instead. + fn discriminant_span(&self) -> Span { + match &self.discriminant { + Some(discriminant) => discriminant.span(), + None => self.name.span(), + } + } +} diff --git a/godot-macros/src/derive/data_models/godot_attribute.rs b/godot-macros/src/derive/data_models/godot_attribute.rs new file mode 100644 index 000000000..42bd2a93d --- /dev/null +++ b/godot-macros/src/derive/data_models/godot_attribute.rs @@ -0,0 +1,90 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; +use venial::Declaration; + +use crate::util::{bail, KvParser}; +use crate::ParseResult; + +/// Stores data related to the `#[godot(..)]` attribute. +pub enum GodotAttribute { + /// `#[godot(transparent)]` + Transparent { span: Span }, + /// `#[godot(via = via_type)]` + Via { span: Span, via_type: ViaType }, +} + +impl GodotAttribute { + pub fn parse_attribute(declaration: &Declaration) -> ParseResult { + let mut parser = KvParser::parse_required(declaration.attributes(), "godot", declaration)?; + let attribute = Self::parse(&mut parser)?; + parser.finish()?; + + Ok(attribute) + } + + fn parse(parser: &mut KvParser) -> ParseResult { + let span = parser.span(); + + if parser.handle_alone("transparent")? { + return Ok(Self::Transparent { span }); + } + + if let Some(via_type) = parser.handle_ident("via")? { + return Ok(Self::Via { + span, + via_type: ViaType::parse_ident(via_type)?, + }); + } + + bail!( + span, + "expected either `#[godot(transparent)]` or `#[godot(via = )]`" + ) + } + + /// The span of the entire attribute. + /// + /// Specifically this is the span of the `[ ]` group from a `#[godot(...)]` attribute. + pub fn span(&self) -> Span { + match self { + GodotAttribute::Transparent { span } => *span, + GodotAttribute::Via { span, .. } => *span, + } + } +} + +/// The via type from a `#[godot(via = via_type)]` attribute. +pub enum ViaType { + /// The via type is `GString` + GString { gstring_ident: Ident }, + /// The via type is an integer + Int { int_ident: Ident }, +} + +impl ViaType { + fn parse_ident(ident: Ident) -> ParseResult { + let via_type = match ident.to_string().as_str() { + "GString" => ViaType::GString { gstring_ident: ident }, + "i8" |"i16" | "i32" | "i64" | "u8" | "u16" | "u32" => ViaType::Int { int_ident: ident }, + other => return bail!(ident, "Via type `{other}` is not supported, expected one of: GString, i8, i16, i32, i64, u8, u16, u32") + }; + + Ok(via_type) + } +} + +impl ToTokens for ViaType { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + ViaType::GString { gstring_ident } => gstring_ident.to_tokens(tokens), + ViaType::Int { int_ident } => int_ident.to_tokens(tokens), + } + } +} diff --git a/godot-macros/src/derive/data_models/godot_convert.rs b/godot-macros/src/derive/data_models/godot_convert.rs new file mode 100644 index 000000000..1e194f4a9 --- /dev/null +++ b/godot-macros/src/derive/data_models/godot_convert.rs @@ -0,0 +1,117 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::{Ident, TokenStream}; +use quote::ToTokens; +use venial::Declaration; + +use crate::util::bail; +use crate::ParseResult; + +use super::c_style_enum::CStyleEnum; +use super::godot_attribute::{GodotAttribute, ViaType}; +use super::newtype::NewtypeStruct; + +/// Stores all relevant data to derive `GodotConvert` and other related traits. +pub struct GodotConvert { + /// The name of the type we're deriving for. + pub ty_name: Ident, + /// The data from the type and `godot` attribute. + pub convert_type: ConvertType, +} + +impl GodotConvert { + pub fn parse_declaration(declaration: Declaration) -> ParseResult { + let (name, where_clause, generic_params) = match &declaration { + venial::Declaration::Struct(struct_) => ( + struct_.name.clone(), + &struct_.where_clause, + &struct_.generic_params, + ), + venial::Declaration::Enum(enum_) => ( + enum_.name.clone(), + &enum_.where_clause, + &enum_.generic_params, + ), + other => { + return bail!( + other, + "`GodotConvert` only supports structs and enums currently" + ) + } + }; + + if let Some(where_clause) = where_clause { + return bail!( + where_clause, + "`GodotConvert` does not support where clauses" + ); + } + + if let Some(generic_params) = generic_params { + return bail!(generic_params, "`GodotConvert` does not support generics"); + } + + let data = ConvertType::parse_declaration(declaration)?; + + Ok(Self { + ty_name: name, + convert_type: data, + }) + } +} + +/// Stores what kind of `GodotConvert` derive we're doing. +pub enum ConvertType { + /// Deriving for a newtype struct. + NewType { field: NewtypeStruct }, + /// Deriving for an enum. + Enum { variants: CStyleEnum, via: ViaType }, +} + +impl ConvertType { + pub fn parse_declaration(declaration: Declaration) -> ParseResult { + let attribute = GodotAttribute::parse_attribute(&declaration)?; + + match &declaration { + Declaration::Struct(struct_) => { + let GodotAttribute::Transparent { .. } = attribute else { + return bail!(attribute.span(), "`GodotConvert` on structs only works with `#[godot(transparent)]` currently"); + }; + + Ok(Self::NewType { + field: NewtypeStruct::parse_struct(struct_)?, + }) + } + Declaration::Enum(enum_) => { + let GodotAttribute::Via { via_type, .. } = attribute else { + return bail!( + attribute.span(), + "`GodotConvert` on enums requires `#[godot(via = ...)]` currently" + ); + }; + + Ok(Self::Enum { + variants: CStyleEnum::parse_enum(enum_)?, + via: via_type, + }) + } + _ => bail!( + declaration, + "`GodotConvert` only supports structs and enums currently" + ), + } + } + + /// Returns the type for use in `type Via = ;` in `GodotConvert` implementations. + pub fn via_type(&self) -> TokenStream { + match self { + ConvertType::NewType { field } => field.ty.to_token_stream(), + ConvertType::Enum { via, .. } => via.to_token_stream(), + } + } +} diff --git a/godot-macros/src/derive/data_models/mod.rs b/godot-macros/src/derive/data_models/mod.rs new file mode 100644 index 000000000..6470cc4ff --- /dev/null +++ b/godot-macros/src/derive/data_models/mod.rs @@ -0,0 +1,16 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +mod c_style_enum; +mod godot_attribute; +mod godot_convert; +mod newtype; + +pub use c_style_enum::*; +pub use godot_attribute::*; +pub use godot_convert::*; +pub use newtype::*; diff --git a/godot-macros/src/derive/data_models/newtype.rs b/godot-macros/src/derive/data_models/newtype.rs new file mode 100644 index 000000000..317d9fdfd --- /dev/null +++ b/godot-macros/src/derive/data_models/newtype.rs @@ -0,0 +1,70 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use venial::TyExpr; + +use crate::util::bail; +use crate::ParseResult; + +/// Stores info from the field of a newtype struct for use in deriving `GodotConvert` and other related traits. +pub struct NewtypeStruct { + /// The name of the field. + /// + /// If `None`, then this is represents a tuple-struct with one field. + pub name: Option, + /// The type of the field. + pub ty: TyExpr, +} + +impl NewtypeStruct { + /// Parses a struct into a newtype struct. + /// + /// This will fail if the struct doesn't have exactly one field. + pub fn parse_struct(struct_: &venial::Struct) -> ParseResult { + match &struct_.fields { + venial::StructFields::Unit => bail!(&struct_.fields, "GodotConvert expects a struct with a single field, unit structs are currently not supported"), + venial::StructFields::Tuple(fields) => { + if fields.fields.len() != 1 { + return bail!(&fields.fields, "GodotConvert expects a struct with a single field, not {} fields", fields.fields.len()) + } + + let (field, _) = fields.fields[0].clone(); + + Ok(NewtypeStruct { name: None, ty: field.ty }) + }, + venial::StructFields::Named(fields) => { + if fields.fields.len() != 1 { + return bail!(&fields.fields, "GodotConvert expects a struct with a single field, not {} fields", fields.fields.len()) + } + + let (field, _) = fields.fields[0].clone(); + + Ok(NewtypeStruct { name: Some(field.name), ty: field.ty }) + }, + } + } + + /// Gets the field name. + /// + /// If this represents a tuple-struct, then it will return `0`. This can be used just like it was a named field with the name `0`. + /// For instance: + /// ``` + /// struct Foo(i64); + /// + /// let mut foo = Foo { 0: 10 }; + /// foo.0 = 20; + /// println!("{}", foo.0); + /// ``` + pub fn field_name(&self) -> TokenStream { + match &self.name { + Some(name) => quote! { #name }, + None => quote! { 0 }, + } + } +} diff --git a/godot-macros/src/derive/derive_export.rs b/godot-macros/src/derive/derive_export.rs index 058717e73..a26f17edb 100644 --- a/godot-macros/src/derive/derive_export.rs +++ b/godot-macros/src/derive/derive_export.rs @@ -11,10 +11,13 @@ use venial::Declaration; use crate::ParseResult; -use super::data_model::GodotConvert; +use crate::derive::data_models::GodotConvert; +/// Derives `Export` for the declaration. +/// +/// This currently just reuses the property hint from the `Var` implementation. pub fn derive_export(declaration: Declaration) -> ParseResult { - let GodotConvert { name, .. } = GodotConvert::parse_declaration(declaration)?; + let GodotConvert { ty_name: name, .. } = GodotConvert::parse_declaration(declaration)?; Ok(quote! { impl ::godot::register::property::Export for #name { diff --git a/godot-macros/src/derive/derive_from_godot.rs b/godot-macros/src/derive/derive_from_godot.rs index 2afcd42bc..598ce0401 100644 --- a/godot-macros/src/derive/derive_from_godot.rs +++ b/godot-macros/src/derive/derive_from_godot.rs @@ -5,56 +5,56 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use proc_macro2::{Ident, Literal, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::quote; -use venial::Declaration; -use crate::derive::data_model::{ConvertData, GodotConvert, ViaType}; -use crate::ParseResult; +use crate::derive::data_models::{CStyleEnum, ConvertType, GodotConvert, NewtypeStruct, ViaType}; -use super::data_model::{CStyleEnum, NewtypeField}; - -pub fn derive_from_godot(declaration: Declaration) -> ParseResult { - let GodotConvert { name, data } = GodotConvert::parse_declaration(declaration)?; +/// Creates a `FromGodot` impl for the given `GodotConvert`. +/// +/// There is no dedicated `FromGodot` derive macro currently, this is instead called by the `GodotConvert` derive macro. +pub fn make_fromgodot(convert: &GodotConvert) -> TokenStream { + let GodotConvert { + ty_name: name, + convert_type: data, + } = convert; match data { - ConvertData::NewType { field } => from_newtype(name, field), - ConvertData::Enum { + ConvertType::NewType { field } => make_fromgodot_for_newtype_struct(name, field), + ConvertType::Enum { variants, - via: ViaType::GString(_), - } => from_enum_string(name, variants), - ConvertData::Enum { + via: ViaType::GString { .. }, + } => make_fromgodot_for_gstring_enum(name, variants), + ConvertType::Enum { variants, - via: ViaType::Int(_, int), - } => from_enum_int(name, variants, int.to_ident()), + via: ViaType::Int { int_ident }, + } => make_fromgodot_for_int_enum(name, variants, int_ident), } } -fn from_newtype(name: Ident, field: NewtypeField) -> ParseResult { +/// Derives `FromGodot` for newtype structs. +fn make_fromgodot_for_newtype_struct(name: &Ident, field: &NewtypeStruct) -> TokenStream { // For tuple structs this ends up using the alternate tuple-struct constructor syntax of - // TupleStruct { .0: value } + // TupleStruct { 0: value } let field_name = field.field_name(); - let via_type = field.ty; + let via_type = &field.ty; - Ok(quote! { + quote! { impl ::godot::builtin::meta::FromGodot for #name { fn try_from_godot(via: #via_type) -> ::std::result::Result { Ok(Self { #field_name: via }) } } - }) + } } -fn from_enum_int(name: Ident, enum_: CStyleEnum, int: Ident) -> ParseResult { - let discriminants = enum_ - .discriminants() - .iter() - .map(|i| Literal::i64_unsuffixed(*i)) - .collect::>(); +/// Derives `FromGodot` for enums with a via type of integers. +fn make_fromgodot_for_int_enum(name: &Ident, enum_: &CStyleEnum, int: &Ident) -> TokenStream { + let discriminants = enum_.discriminants(); let names = enum_.names(); let bad_variant_error = format!("invalid {name} variant"); - Ok(quote! { + quote! { impl ::godot::builtin::meta::FromGodot for #name { fn try_from_godot(via: #int) -> ::std::result::Result { match via { @@ -65,15 +65,16 @@ fn from_enum_int(name: Ident, enum_: CStyleEnum, int: Ident) -> ParseResult ParseResult { +/// Derives `FromGodot` for enums with a via type of `GString`. +fn make_fromgodot_for_gstring_enum(name: &Ident, enum_: &CStyleEnum) -> TokenStream { let names = enum_.names(); let names_str = names.iter().map(ToString::to_string).collect::>(); let bad_variant_error = format!("invalid {name} variant"); - Ok(quote! { + quote! { impl ::godot::builtin::meta::FromGodot for #name { fn try_from_godot(via: ::godot::builtin::GString) -> ::std::result::Result { match via.to_string().as_str() { @@ -84,5 +85,5 @@ fn from_enum_string(name: Ident, enum_: CStyleEnum) -> ParseResult } } } - }) + } } diff --git a/godot-macros/src/derive/derive_godot_convert.rs b/godot-macros/src/derive/derive_godot_convert.rs index d54258842..7e85d8ad4 100644 --- a/godot-macros/src/derive/derive_godot_convert.rs +++ b/godot-macros/src/derive/derive_godot_convert.rs @@ -9,18 +9,28 @@ use proc_macro2::TokenStream; use quote::quote; use venial::Declaration; +use crate::derive::data_models::GodotConvert; +use crate::derive::{make_fromgodot, make_togodot}; use crate::ParseResult; -use crate::derive::data_model::GodotConvert; - +/// Derives `GodotConvert` for the given declaration. +/// +/// This also derives `FromGodot` and `ToGodot`. pub fn derive_godot_convert(declaration: Declaration) -> ParseResult { - let GodotConvert { name, data } = GodotConvert::parse_declaration(declaration)?; + let convert = GodotConvert::parse_declaration(declaration)?; + + let name = &convert.ty_name; + let via_type = convert.convert_type.via_type(); - let via_type = data.via_type(); + let to_godot_impl = make_togodot(&convert); + let from_godot_impl = make_fromgodot(&convert); Ok(quote! { impl ::godot::builtin::meta::GodotConvert for #name { type Via = #via_type; } + + #to_godot_impl + #from_godot_impl }) } diff --git a/godot-macros/src/derive/derive_to_godot.rs b/godot-macros/src/derive/derive_to_godot.rs index 8b7fda639..86ce4eedb 100644 --- a/godot-macros/src/derive/derive_to_godot.rs +++ b/godot-macros/src/derive/derive_to_godot.rs @@ -5,36 +5,39 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use proc_macro2::{Ident, Literal, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::quote; -use venial::Declaration; -use crate::derive::data_model::{ConvertData, GodotConvert, ViaType}; -use crate::ParseResult; +use crate::derive::data_models::{CStyleEnum, ConvertType, GodotConvert, NewtypeStruct, ViaType}; -use super::data_model::{CStyleEnum, NewtypeField}; - -pub fn derive_to_godot(declaration: Declaration) -> ParseResult { - let GodotConvert { name, data } = GodotConvert::parse_declaration(declaration)?; +/// Creates a `ToGodot` impl for the given `GodotConvert`. +/// +/// There is no dedicated `ToGodot` derive macro currently, this is instead called by the `GodotConvert` derive macro. +pub fn make_togodot(convert: &GodotConvert) -> TokenStream { + let GodotConvert { + ty_name: name, + convert_type: data, + } = convert; match data { - ConvertData::NewType { field } => to_newtype(name, field), - ConvertData::Enum { + ConvertType::NewType { field } => make_togodot_for_newtype_struct(name, field), + ConvertType::Enum { variants, - via: ViaType::GString(_), - } => to_enum_string(name, variants), - ConvertData::Enum { + via: ViaType::GString { .. }, + } => make_togodot_for_string_enum(name, variants), + ConvertType::Enum { variants, - via: ViaType::Int(_, int), - } => to_enum_int(name, variants, int.to_ident()), + via: ViaType::Int { int_ident }, + } => make_togodot_for_int_enum(name, variants, int_ident), } } -fn to_newtype(name: Ident, field: NewtypeField) -> ParseResult { +/// Derives `ToGodot` for newtype structs. +fn make_togodot_for_newtype_struct(name: &Ident, field: &NewtypeStruct) -> TokenStream { let field_name = field.field_name(); - let via_type = field.ty; + let via_type = &field.ty; - Ok(quote! { + quote! { impl ::godot::builtin::meta::ToGodot for #name { fn to_godot(&self) -> #via_type { ::godot::builtin::meta::ToGodot::to_godot(&self.#field_name) @@ -44,18 +47,15 @@ fn to_newtype(name: Ident, field: NewtypeField) -> ParseResult { ::godot::builtin::meta::ToGodot::into_godot(self.#field_name) } } - }) + } } -fn to_enum_int(name: Ident, enum_: CStyleEnum, int: Ident) -> ParseResult { - let discriminants = enum_ - .discriminants() - .iter() - .map(|i| Literal::i64_unsuffixed(*i)) - .collect::>(); +/// Derives `ToGodot` for enums with a via type of integers. +fn make_togodot_for_int_enum(name: &Ident, enum_: &CStyleEnum, int: &Ident) -> TokenStream { + let discriminants = enum_.discriminants(); let names = enum_.names(); - Ok(quote! { + quote! { impl ::godot::builtin::meta::ToGodot for #name { fn to_godot(&self) -> #int { match self { @@ -65,14 +65,15 @@ fn to_enum_int(name: Ident, enum_: CStyleEnum, int: Ident) -> ParseResult ParseResult { +/// Derives `ToGodot` for enums with a via type of `GString`. +fn make_togodot_for_string_enum(name: &Ident, enum_: &CStyleEnum) -> TokenStream { let names = enum_.names(); let names_str = names.iter().map(ToString::to_string).collect::>(); - Ok(quote! { + quote! { impl ::godot::builtin::meta::ToGodot for #name { fn to_godot(&self) -> ::godot::builtin::GString { match self { @@ -82,5 +83,5 @@ fn to_enum_string(name: Ident, enum_: CStyleEnum) -> ParseResult { } } } - }) + } } diff --git a/godot-macros/src/derive/derive_var.rs b/godot-macros/src/derive/derive_var.rs index c4bc0408e..c98b63377 100644 --- a/godot-macros/src/derive/derive_var.rs +++ b/godot-macros/src/derive/derive_var.rs @@ -9,16 +9,18 @@ use proc_macro2::TokenStream; use quote::quote; use venial::Declaration; +use crate::derive::data_models::GodotConvert; use crate::ParseResult; -use super::data_model::GodotConvert; - +/// Derives `Var` for the given declaration. +/// +/// This uses `ToGodot` and `FromGodot` for the `get_property` and `set_property` implementations. pub fn derive_var(declaration: Declaration) -> ParseResult { let convert = GodotConvert::parse_declaration(declaration)?; let property_hint_impl = create_property_hint_impl(&convert); - let name = convert.name; + let name = convert.ty_name; Ok(quote! { impl ::godot::register::property::Var for #name { @@ -38,11 +40,14 @@ pub fn derive_var(declaration: Declaration) -> ParseResult { }) } +/// Make an appropriate property hint implementation. +/// +/// For newtype structs we just defer to the wrapped type. For enums we use `PropertyHint::ENUM` with an appropriate hint string. fn create_property_hint_impl(convert: &GodotConvert) -> TokenStream { - use super::data_model::ConvertData as Data; - use super::data_model::ViaType; + use super::data_models::ConvertType as Data; + use super::data_models::ViaType; - match &convert.data { + match &convert.convert_type { Data::NewType { field } => { let ty = &field.ty; quote! { @@ -51,8 +56,8 @@ fn create_property_hint_impl(convert: &GodotConvert) -> TokenStream { } Data::Enum { variants, via } => { let hint_string = match via { - ViaType::GString(_) => variants.to_string_hint(), - ViaType::Int(_, _) => variants.to_int_hint(), + ViaType::GString { .. } => variants.to_string_hint(), + ViaType::Int { .. } => variants.to_int_hint(), }; quote! { diff --git a/godot-macros/src/derive/mod.rs b/godot-macros/src/derive/mod.rs index 6641df0a3..6ec84faa1 100644 --- a/godot-macros/src/derive/mod.rs +++ b/godot-macros/src/derive/mod.rs @@ -7,7 +7,7 @@ //! Derive macros on types outside of classes. -mod data_model; +mod data_models; mod derive_export; mod derive_from_godot; mod derive_godot_convert; diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index f96fa0500..f0ebadfdd 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -526,125 +526,149 @@ pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream { translate(input, class::attribute_godot_api) } -/// Derive macro for [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) on structs (required by [`ToGodot`] and [`FromGodot`]). -#[proc_macro_derive(GodotConvert, attributes(godot))] -pub fn derive_godot_convert(input: TokenStream) -> TokenStream { - translate(input, derive::derive_godot_convert) -} - -/// Derive macro for [`ToGodot`](../builtin/meta/trait.ToGodot.html) on structs or enums. +/// Derive macro for [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) on structs. +/// +/// This derive macro also derives [`ToGodot`](../builtin/meta/trait.ToGodot.html) and [`FromGodot`](../builtin/meta/trait.FromGodot.html). +/// +/// # Choosing a Via type /// -/// # Example +/// To specify the `Via` type that your type should be converted to, you must use the `godot` attribute. +/// There are currently two modes supported. +/// +/// ## `transparent` +/// +/// If you specify `#[godot(transparent)]` on single-field struct, your struct will be treated as a newtype struct. This means that all derived +/// operations on the struct will defer to the type of that single field. +/// +/// ### Example /// /// ```no_run -/// # use godot::prelude::*; -/// #[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -/// struct StructNamed { -/// field1: String, -/// field2: i32, +/// use godot::prelude::*; +/// +/// #[derive(GodotConvert)] +/// #[godot(transparent)] +/// struct CustomVector2(Vector2); +/// +/// let obj = CustomVector2(Vector2::new(10.0, 25.0)); +/// assert_eq!(obj.to_godot(), Vector2::new(10.0, 25.0)); +/// ``` +/// +/// This also works for named structs with a single field: +/// ```no_run +/// use godot::prelude::*; +/// +/// #[derive(GodotConvert)] +/// #[godot(transparent)] +/// struct MyNewtype { +/// string: GString, /// } /// -/// let obj = StructNamed { -/// field1: "1".to_string(), -/// field2: 2, +/// let obj = MyNewtype { +/// string: "hello!".into(), /// }; -/// let dict = dict! { -/// "StructNamed": dict! { -/// "field1": "four", -/// "field2": 5, -/// } -/// }; -/// -/// // This would not panic. -/// assert_eq!(obj.to_variant(), dict.to_variant()); +/// assert_eq!(obj.to_godot(), GString::from("hello!")); /// ``` /// -/// You can use the `#[skip]` attribute to ignore a field from being converted to `ToGodot`. -#[proc_macro_derive(ToGodot, attributes(godot))] -pub fn derive_to_godot(input: TokenStream) -> TokenStream { - translate(input, derive::derive_to_godot) -} - -/// Derive macro for [`FromGodot`](../builtin/meta/trait.FromGodot.html) on structs or enums. +/// However it will not work for structs with more than one field, even if that field is zero sized: +/// ```compile_fail +/// use godot::prelude::*; +/// +/// #[derive(GodotConvert)] +/// #[godot(transparent)] +/// struct SomeNewtype { +/// int: i64, +/// zst: (), +/// } +/// ``` /// -/// # Example +/// You can also not use `transparent` with enums: +/// ```compile_fail +/// use godot::prelude::*; /// -/// ```no_run -/// # use godot::prelude::*; -/// #[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] -/// struct StructNamed { -/// field1: String, -/// field2: i32, +/// #[derive(GodotConvert)] +/// #[godot(transparent)] +/// enum MyEnum { +/// Int(i64) /// } +/// ``` /// -/// let obj = StructNamed { -/// field1: "1".to_string(), -/// field2: 2, -/// }; -/// let dict_variant = dict! { -/// "StructNamed": dict! { -/// "field1": "four", -/// "field2": 5, -/// } -/// }.to_variant(); -/// -/// // This would not panic. -/// assert_eq!(StructNamed::from_variant(&dict_variant), obj); -/// ``` -/// -/// You can use the skip attribute to ignore a field from the provided variant and use `Default::default()` -/// to get it instead. -#[proc_macro_derive(FromGodot, attributes(godot))] -pub fn derive_from_godot(input: TokenStream) -> TokenStream { - translate(input, derive::derive_from_godot) -} - -/// Derive macro for [`Var`](../register/property/trait.Var.html) on enums. +/// ## `via = ` +/// +/// For c-style enums, that is enums where all the variants are unit-like, you can use `via = ` to convert the enum into that +/// type. /// -/// This also requires deriving `GodotConvert`. +/// The types you can use this with currently are: +/// - `GString` +/// - `i8`, `i16`, `i32`, `i64` +/// - `u8`, `u16`, `u32` /// -/// Currently has some tight requirements which are expected to be softened as implementation expands: -/// - Only works for enums, structs aren't supported by this derive macro at the moment. -/// - The enum must have an explicit `#[repr(u*/i*)]` type. -/// - This will likely stay this way, since `isize`, the default repr type, is not a concept in Godot. -/// - The enum variants must not have any fields - currently only unit variants are supported. -/// - The enum variants must have explicit discriminants, that is, e.g. `A = 2`, not just `A` +/// When using one of the integer types, each variant of the enum will be converted into its discriminant. /// -/// # Example +/// ### Examples /// /// ```no_run -/// # use godot::prelude::*; -/// #[derive(Var, GodotConvert)] -/// #[repr(i32)] -/// # #[derive(Eq, PartialEq, Debug)] +/// use godot::prelude::*; +/// #[derive(GodotConvert)] +/// #[godot(via = GString)] /// enum MyEnum { -/// A = 0, -/// B = 1, +/// A, +/// B, +/// C, /// } /// -/// #[derive(GodotClass)] -/// #[class(no_init)] // No Godot default constructor. -/// struct MyClass { -/// #[var] -/// foo: MyEnum, +/// assert_eq!(MyEnum::A.to_godot(), GString::from("A")); +/// assert_eq!(MyEnum::B.to_godot(), GString::from("B")); +/// assert_eq!(MyEnum::C.to_godot(), GString::from("C")); +/// ``` +/// +/// ```no_run +/// use godot::prelude::*; +/// #[derive(GodotConvert)] +/// #[godot(via = i64)] +/// enum MyEnum { +/// A, +/// B, +/// C, /// } /// -/// fn main() { -/// let mut class = MyClass { foo: MyEnum::B }; -/// assert_eq!(class.get_foo(), MyEnum::B as i32); +/// assert_eq!(MyEnum::A.to_godot(), 0); +/// assert_eq!(MyEnum::B.to_godot(), 1); +/// assert_eq!(MyEnum::C.to_godot(), 2); +/// ``` +/// +/// Explicit discriminants are used for integers: /// -/// class.set_foo(MyEnum::A as i32); -/// assert_eq!(class.foo, MyEnum::A); +/// ```no_run +/// use godot::prelude::*; +/// #[derive(GodotConvert)] +/// #[godot(via = u8)] +/// enum MyEnum { +/// A, +/// B = 10, +/// C, /// } +/// +/// assert_eq!(MyEnum::A.to_godot(), 0); +/// assert_eq!(MyEnum::B.to_godot(), 10); +/// assert_eq!(MyEnum::C.to_godot(), 11); /// ``` +#[proc_macro_derive(GodotConvert, attributes(godot))] +pub fn derive_godot_convert(input: TokenStream) -> TokenStream { + translate(input, derive::derive_godot_convert) +} + +/// Derive macro for [`Var`](../register/property/trait.Var.html) on enums. +/// +/// This expects a derived [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) implementation, using a manual +/// implementation of `GodotConvert` may lead to incorrect values being displayed in Godot. #[proc_macro_derive(Var, attributes(godot))] -pub fn derive_property(input: TokenStream) -> TokenStream { +pub fn derive_var(input: TokenStream) -> TokenStream { translate(input, derive::derive_var) } /// Derive macro for [`Export`](../register/property/trait.Export.html) on enums. /// -/// Currently has some tight requirements which are expected to be softened as implementation expands, see requirements for [`Var`]. +/// See also [`Var`]. #[proc_macro_derive(Export, attributes(godot))] pub fn derive_export(input: TokenStream) -> TokenStream { translate(input, derive::derive_export) diff --git a/godot-macros/src/util/kv_parser.rs b/godot-macros/src/util/kv_parser.rs index c28982aa9..34dfb9e20 100644 --- a/godot-macros/src/util/kv_parser.rs +++ b/godot-macros/src/util/kv_parser.rs @@ -388,13 +388,6 @@ impl<'a> ParserState<'a> { } } -pub(crate) fn has_attr(attributes: &[Attribute], expected: &str, key: &str) -> bool { - match KvParser::parse(attributes, expected) { - Ok(Some(mut kvp)) => kvp.handle_alone(key).unwrap(), - _ => false, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/godot-macros/src/util/mod.rs b/godot-macros/src/util/mod.rs index faa6b1157..9a1dd4efc 100644 --- a/godot-macros/src/util/mod.rs +++ b/godot-macros/src/util/mod.rs @@ -7,18 +7,15 @@ // Note: some code duplication with codegen crate -use std::collections::HashMap; - use crate::ParseResult; use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree}; use quote::spanned::Spanned; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use venial::{Error, Function, GenericParamList, Impl, TyExpr, WhereClause}; +use venial::{Error, Function, Impl, TyExpr}; mod kv_parser; mod list_parser; -pub(crate) use kv_parser::has_attr; pub(crate) use kv_parser::KvParser; pub(crate) use list_parser::ListParser; @@ -249,40 +246,6 @@ pub(crate) fn extract_cfg_attrs( }) } -pub(crate) struct DeclInfo { - pub where_: Option, - pub generic_params: Option, - pub name: Ident, - pub name_string: String, -} - -pub(crate) fn decl_get_info(decl: &venial::Declaration) -> DeclInfo { - let (where_, generic_params, name, name_string) = match decl { - venial::Declaration::Struct(struct_) => ( - struct_.where_clause.clone(), - struct_.generic_params.clone(), - struct_.name.clone(), - struct_.name.to_string(), - ), - venial::Declaration::Enum(enum_) => ( - enum_.where_clause.clone(), - enum_.generic_params.clone(), - enum_.name.clone(), - enum_.name.to_string(), - ), - _ => { - panic!("only enums and structs are supported at the moment") - } - }; - - DeclInfo { - where_, - generic_params, - name, - name_string, - } -} - pub fn make_virtual_tool_check() -> TokenStream { quote! { if ::godot::private::is_class_inactive(Self::__config().is_tool) { diff --git a/godot/src/lib.rs b/godot/src/lib.rs index b212666b9..8e8c85158 100644 --- a/godot/src/lib.rs +++ b/godot/src/lib.rs @@ -203,7 +203,7 @@ pub mod init { /// Register/export Rust symbols to Godot: classes, methods, enums... pub mod register { pub use godot_core::property; - pub use godot_macros::{godot_api, Export, FromGodot, GodotClass, GodotConvert, ToGodot, Var}; + pub use godot_macros::{godot_api, Export, GodotClass, GodotConvert, Var}; } /// Testing facilities (unstable). diff --git a/godot/src/prelude.rs b/godot/src/prelude.rs index 816e96996..ecbdf6cfc 100644 --- a/godot/src/prelude.rs +++ b/godot/src/prelude.rs @@ -8,7 +8,7 @@ pub use super::register::property::{Export, TypeStringHint, Var}; // Re-export macros. -pub use super::register::{godot_api, Export, FromGodot, GodotClass, GodotConvert, ToGodot, Var}; +pub use super::register::{godot_api, Export, GodotClass, GodotConvert, Var}; pub use super::builtin::__prelude_reexport::*; pub use super::builtin::math::FloatExt as _; diff --git a/itest/rust/src/object_tests/property_test.rs b/itest/rust/src/object_tests/property_test.rs index ad7b24436..dceb651d9 100644 --- a/itest/rust/src/object_tests/property_test.rs +++ b/itest/rust/src/object_tests/property_test.rs @@ -11,7 +11,7 @@ use godot::engine::global::{PropertyHint, PropertyUsageFlags}; use godot::engine::{INode, IRefCounted, Node, Object, RefCounted, Texture}; use godot::obj::{Base, EngineBitfield, EngineEnum, Gd, NewAlloc, NewGd}; use godot::register::property::{Export, PropertyHintInfo, Var}; -use godot::register::{godot_api, Export, FromGodot, GodotClass, GodotConvert, ToGodot, Var}; +use godot::register::{godot_api, Export, GodotClass, GodotConvert, Var}; use godot::test::itest; // No tests currently, tests using these classes are in Godot scripts. @@ -303,8 +303,9 @@ struct CheckAllExports { color_no_alpha: Color, } -#[derive(GodotConvert, ToGodot, FromGodot, Var, Export, Eq, PartialEq, Debug)] +#[derive(GodotConvert, Var, Export, Eq, PartialEq, Debug)] #[godot(via = i64)] +#[repr(i64)] pub enum TestEnum { A = 0, B = 1, diff --git a/itest/rust/src/register_tests/derive_variant_test.rs b/itest/rust/src/register_tests/derive_variant_test.rs index 1921c7596..57573b16e 100644 --- a/itest/rust/src/register_tests/derive_variant_test.rs +++ b/itest/rust/src/register_tests/derive_variant_test.rs @@ -7,9 +7,9 @@ use std::fmt::Debug; -use godot::builtin::meta::{FromGodot, ToGodot}; -use godot::builtin::{dict, varray, GString, Variant, Vector2}; -use godot::register::{FromGodot, GodotConvert, ToGodot}; +use godot::builtin::meta::ToGodot; +use godot::builtin::{GString, Vector2}; +use godot::register::GodotConvert; use crate::common::roundtrip; use crate::framework::itest; @@ -17,17 +17,17 @@ use crate::framework::itest; // ---------------------------------------------------------------------------------------------------------------------------------------------- // General FromGodot/ToGodot derive tests -#[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] +#[derive(GodotConvert, PartialEq, Debug)] #[godot(transparent)] struct TupleNewtype(GString); -#[derive(FromGodot, ToGodot, GodotConvert, PartialEq, Debug)] +#[derive(GodotConvert, PartialEq, Debug)] #[godot(transparent)] struct NamedNewtype { field1: Vector2, } -#[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] +#[derive(GodotConvert, Clone, PartialEq, Debug)] #[godot(via = GString)] enum EnumStringy { A, @@ -36,7 +36,7 @@ enum EnumStringy { D = 50, } -#[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] +#[derive(GodotConvert, Clone, PartialEq, Debug)] #[godot(via = i64)] enum EnumInty { A = 10, @@ -61,32 +61,34 @@ fn newtype_named_struct() { #[itest] fn enum_stringy() { roundtrip(EnumStringy::A); - assert_eq!(EnumStringy::A.to_godot(), "A".into()); roundtrip(EnumStringy::B); - assert_eq!(EnumStringy::B.to_godot(), "B".into()); roundtrip(EnumStringy::C); - assert_eq!(EnumStringy::C.to_godot(), "C".into()); roundtrip(EnumStringy::D); + + assert_eq!(EnumStringy::A.to_godot(), "A".into()); + assert_eq!(EnumStringy::B.to_godot(), "B".into()); + assert_eq!(EnumStringy::C.to_godot(), "C".into()); assert_eq!(EnumStringy::D.to_godot(), "D".into()); } #[itest] fn enum_inty() { roundtrip(EnumInty::A); - assert_eq!(EnumInty::A.to_godot(), 10); roundtrip(EnumInty::B); - assert_eq!(EnumInty::B.to_godot(), 0); roundtrip(EnumInty::C); - assert_eq!(EnumInty::C.to_godot(), 2); roundtrip(EnumInty::D); - assert_eq!(EnumInty::D.to_godot(), 1); roundtrip(EnumInty::E); - assert_eq!(EnumInty::E.to_godot(), 3); + + assert_eq!(EnumInty::A.to_godot(), 10); + assert_eq!(EnumInty::B.to_godot(), 11); + assert_eq!(EnumInty::C.to_godot(), 12); + assert_eq!(EnumInty::D.to_godot(), 1); + assert_eq!(EnumInty::E.to_godot(), 2); } macro_rules! test_inty { ($T:ident, $test_name:ident, $class_name:ident) => { - #[derive(FromGodot, ToGodot, GodotConvert, Clone, PartialEq, Debug)] + #[derive(GodotConvert, Clone, PartialEq, Debug)] #[godot(via = $T)] enum $class_name { A,