Skip to content

Commit

Permalink
Exporting custom rust enums is not supported (#54)
Browse files Browse the repository at this point in the history
Closes #46
  • Loading branch information
TitanNano authored Aug 22, 2024
1 parent be87ea4 commit 45ab022
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 31 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ godot = { git = "https://github.com/godot-rust/gdext", tag = "v0.1.3", features
godot-cell = { git = "https://github.com/godot-rust/gdext", tag = "v0.1.3" }
itertools = "0.10.3"
rand = "0.8.5"
darling = { version = "0.20.10" }
darling = { version = "0.20" }
proc-macro2 = "1.0.68"
quote = "1.0.33"
syn = "2.0.38"
quote = "1"
syn = "2"
const-str = "0.5.6"
thiserror = "1"

Expand Down
1 change: 1 addition & 0 deletions derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ darling.workspace = true
proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true
itertools.workspace = true
4 changes: 2 additions & 2 deletions derive/src/attribute_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

use darling::ast::Data;
use darling::util::{self, WithOriginal};
use darling::util::{self, SpannedValue, WithOriginal};
use darling::{FromAttributes, FromDeriveInput, FromField, FromMeta};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
Expand Down Expand Up @@ -327,7 +327,7 @@ pub struct FieldOpts {
#[darling(supports(struct_any), attributes(script), forward_attrs(doc))]
pub struct GodotScriptOpts {
pub ident: syn::Ident,
pub data: Data<util::Ignored, FieldOpts>,
pub data: Data<util::Ignored, SpannedValue<FieldOpts>>,
pub base: Option<syn::Ident>,
pub attrs: Vec<syn::Attribute>,
}
Expand Down
141 changes: 141 additions & 0 deletions derive/src/enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* 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 http://mozilla.org/MPL/2.0/.
*/

use darling::{
ast::Data,
util::{Ignored, WithOriginal},
FromDeriveInput, FromVariant,
};
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Meta, Visibility};

use crate::type_paths::{convert_error_ty, godot_types, property_hints};

#[derive(FromDeriveInput)]
#[darling(supports(enum_unit), attributes(script_enum))]
struct EnumDeriveInput {
vis: Visibility,
ident: Ident,
export: Option<WithOriginal<(), Meta>>,
data: Data<EnumVariant, Ignored>,
}

#[derive(FromVariant)]
struct EnumVariant {
ident: Ident,
}

pub fn script_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let godot_types = godot_types();
let convert_error = convert_error_ty();
let property_hints = property_hints();

let input = parse_macro_input!(input as DeriveInput);
let input = EnumDeriveInput::from_derive_input(&input).unwrap();

let enum_ident = input.ident;
let enum_as_try_from = quote_spanned! {enum_ident.span()=> <#enum_ident as TryFrom<Self::Via>>};
let enum_from_self = quote_spanned! {enum_ident.span()=> <Self::Via as From<&#enum_ident>>};
let enum_error_ident = Ident::new(&format!("{}Error", enum_ident), enum_ident.span());
let enum_visibility = input.vis;

let variants = input.data.take_enum().unwrap();

let (from_variants, into_variants, hint_strings): (TokenStream, TokenStream, Vec<_>) = variants
.iter()
.enumerate()
.map(|(index, variant)| {
let variant_ident = &variant.ident;
let index = index as u8;

(
quote_spanned! {variant_ident.span()=> #enum_ident::#variant_ident => #index,},
quote_spanned! {variant_ident.span()=> #index => Ok(#enum_ident::#variant_ident),},
format!("{variant_ident}:{index}"),
)
})
.multiunzip();
let enum_property_hint_str = hint_strings.join(",");

let derive_export = input.export.map(|export| {
quote_spanned! {export.original.span()=>
impl ::godot_rust_script::GodotScriptExport for #enum_ident {
fn hint(custom: Option<#property_hints>) -> #property_hints {
if let Some(custom) = custom {
return custom;
}

#property_hints::ENUM
}

fn hint_string(_custom_hint: Option<#property_hints>, custom_string: Option<String>) -> String {
if let Some(custom_string) = custom_string {
return custom_string;
}

String::from(#enum_property_hint_str)
}
}
}
});

let derived = quote! {
impl #godot_types::meta::FromGodot for #enum_ident {
fn try_from_godot(via: Self::Via) -> Result<Self, #convert_error> {
#enum_as_try_from::try_from(via)
.map_err(|err| #convert_error::with_error_value(err, via))
}
}

impl #godot_types::meta::ToGodot for #enum_ident {
fn to_godot(&self) -> Self::Via {
#enum_from_self::from(self)
}
}

impl #godot_types::meta::GodotConvert for #enum_ident {
type Via = u8;
}

impl GodotScriptEnum for #enum_ident {}

impl From<&#enum_ident> for u8 {
fn from(value: &#enum_ident) -> Self {
match value {
#from_variants
}
}
}

#[derive(Debug)]
#enum_visibility struct #enum_error_ident(u8);

impl ::std::fmt::Display for #enum_error_ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Enum value {} is out of range.", self.0)
}
}

impl ::std::error::Error for #enum_error_ident {}

impl TryFrom<u8> for #enum_ident {
type Error = #enum_error_ident;

fn try_from(value: u8) -> ::std::result::Result<Self, Self::Error> {
match value {
#into_variants
_ => Err(#enum_error_ident(value)),
}
}
}

#derive_export
};

derived.into()
}
51 changes: 32 additions & 19 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
mod attribute_ops;
mod impl_attribute;
mod type_paths;
mod enums;

use attribute_ops::{FieldOpts, GodotScriptOpts};
use darling::{ FromAttributes, FromDeriveInput};
use darling::{ util::SpannedValue, FromAttributes, FromDeriveInput};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Type};
use type_paths::{godot_types, string_name_ty, variant_ty};
use type_paths::{godot_types, property_hints, string_name_ty, variant_ty};

use crate::attribute_ops::{FieldExportOps, PropertyOpts};

Expand All @@ -27,6 +28,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let variant_ty = variant_ty();
let string_name_ty = string_name_ty();
let call_error_ty = quote!(#godot_types::sys::GDExtensionCallErrorType);
let property_hint_ty = property_hints();

let base_class = opts
.base
Expand Down Expand Up @@ -63,12 +65,17 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
.iter()
.any(|attr| attr.path().is_ident("export"));

let (hint, hint_string) = {
let (hint, hint_string) = exported.then(|| {
let ops = FieldExportOps::from_attributes(&field.attrs)
.map_err(|err| err.write_errors())?;

ops.hint(&field.ty)?
};
ops.hint(&field.ty)
}).transpose()?.unwrap_or_else(|| {
(
quote_spanned!(field.span()=> #property_hint_ty::NONE),
quote_spanned!(field.span()=> String::new()),
)
});

let description = get_field_description(field);
let item = quote! {
Expand Down Expand Up @@ -176,8 +183,9 @@ fn rust_to_variant_type(ty: &syn::Type) -> Result<TokenStream, TokenStream> {
T::Path(path) => Ok(quote_spanned! {
ty.span() => {
use #godot_types::sys::GodotFfi;
use #godot_types::meta::GodotType;

<#path as #godot_types::meta::GodotType>::Ffi::variant_type()
<<#path as #godot_types::meta::GodotConvert>::Via as GodotType>::Ffi::variant_type()
}
}),
T::Verbatim(_) => Err(syn::Error::new(
Expand All @@ -197,8 +205,9 @@ fn rust_to_variant_type(ty: &syn::Type) -> Result<TokenStream, TokenStream> {
Ok(quote_spanned! {
tuple.span() => {
use #godot_types::sys::GodotFfi;
use #godot_types::meta::GodotType;

<#tuple as #godot_types::meta::GodotType>::Ffi::variant_type()
<<#tuple as #godot_types::meta::GodotConvert>::Via as GodotType>::Ffi::variant_type()
}
})
}
Expand All @@ -218,7 +227,7 @@ fn is_context_type(ty: &syn::Type) -> bool {
path.path.segments.last().map(|segment| segment.ident == "Context").unwrap_or(false)
}

fn derive_default_with_base(field_opts: &[FieldOpts]) -> TokenStream {
fn derive_default_with_base(field_opts: &[SpannedValue<FieldOpts>]) -> TokenStream {
let godot_types = godot_types();
let fields: TokenStream = field_opts
.iter()
Expand All @@ -245,7 +254,7 @@ fn derive_default_with_base(field_opts: &[FieldOpts]) -> TokenStream {
}
}

fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a) -> TokenStream {
fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a SpannedValue<FieldOpts>> + 'a) -> TokenStream {
let godot_types = godot_types();
let string_name_ty = string_name_ty();
let variant_ty = variant_ty();
Expand All @@ -262,11 +271,11 @@ fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
};

let accessor = match opts.get {
Some(getter) => quote_spanned!(getter.span() => #getter(&self)),
None => quote!(self.#field_ident),
Some(getter) => quote_spanned!(getter.span()=> #getter(&self)),
None => quote_spanned!(field_ident.span()=> self.#field_ident),
};

quote! {
quote_spanned! {field.ty.span()=>
#[allow(clippy::needless_borrow)]
#field_name => Some(#godot_types::prelude::ToGodot::to_variant(&#accessor)),
}
Expand All @@ -284,7 +293,7 @@ fn derive_get_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
}
}

fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a) -> TokenStream {
fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a SpannedValue<FieldOpts>> + 'a) -> TokenStream {
let string_name_ty = string_name_ty();
let variant_ty = variant_ty();
let godot_types = godot_types();
Expand All @@ -300,15 +309,14 @@ fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
Err(err) => return err.write_errors(),
};

let variant_value = quote!(#godot_types::prelude::FromGodot::try_from_variant(&value));
let variant_value = quote_spanned!(field.ty.span()=> #godot_types::prelude::FromGodot::try_from_variant(&value));

let assignment = match opts.set {
Some(setter) => quote_spanned!(setter.span() => #setter(self, local_value)),
None => quote!(self.#field_ident = local_value),
Some(setter) => quote_spanned!(setter.span()=> #setter(self, local_value)),
None => quote_spanned!(field.ty.span() => self.#field_ident = local_value),
};

quote_spanned! {
field_ident.span() =>
quote! {
#field_name => {
let local_value = match #variant_value {
Ok(v) => v,
Expand All @@ -334,7 +342,7 @@ fn derive_set_fields<'a>(public_fields: impl Iterator<Item = &'a FieldOpts> + 'a
}

fn derive_property_states_export<'a>(
public_fields: impl Iterator<Item = &'a FieldOpts> + 'a,
public_fields: impl Iterator<Item = &'a SpannedValue<FieldOpts>> + 'a,
) -> TokenStream {
let string_name_ty = string_name_ty();
let variant_ty = variant_ty();
Expand Down Expand Up @@ -420,3 +428,8 @@ fn extract_ident_from_type(impl_target: &syn::Type) -> Result<Ident, TokenStream
_ => Err(compile_error("Unsupported type!", impl_target)),
}
}

#[proc_macro_derive(GodotScriptEnum, attributes(script_enum))]
pub fn script_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
enums::script_enum_derive(input)
}
6 changes: 6 additions & 0 deletions derive/src/type_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ pub fn string_name_ty() -> TokenStream {

quote!(#godot_types::prelude::StringName)
}

pub fn convert_error_ty() -> TokenStream {
let godot_types = godot_types();

quote!(#godot_types::meta::error::ConvertError)
}
3 changes: 3 additions & 0 deletions rust-script/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::{collections::HashMap, fmt::Debug};

use godot::meta::{FromGodot, GodotConvert, ToGodot};
use godot::obj::Inherits;
use godot::prelude::{Gd, Object, StringName, Variant};

Expand Down Expand Up @@ -201,6 +202,8 @@ macro_rules! setup_library {
};
}

pub trait GodotScriptEnum: GodotConvert + FromGodot + ToGodot {}

#[macro_export]
macro_rules! init {
($scripts_module:tt) => {
Expand Down
Loading

0 comments on commit 45ab022

Please sign in to comment.