diff --git a/derive/src/attribute_ops.rs b/derive/src/attribute_ops.rs index 5b7b2a5..758af5f 100644 --- a/derive/src/attribute_ops.rs +++ b/derive/src/attribute_ops.rs @@ -10,12 +10,15 @@ use darling::{FromAttributes, FromDeriveInput, FromField, FromMeta}; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; -use syn::{LitStr, Meta, Type}; +use syn::{LitStr, Meta, PatPath, Type}; use crate::type_paths::godot_types; +#[derive(FromMeta, Debug)] +pub struct FieldSignalOps(pub WithOriginal, Meta>); + #[derive(FromAttributes, Debug)] -#[darling(attributes(export))] +#[darling(attributes(export), forward_attrs)] pub struct FieldExportOps { color_no_alpha: Option>, dir: Option>, @@ -29,47 +32,50 @@ pub struct FieldExportOps { node_path: Option>, placeholder: Option>, range: Option>, + storage: Option>, + custom: Option>, #[darling(rename = "ty")] custom_type: Option>, } -#[derive(FromMeta, Debug)] -pub struct FieldSignalOps(pub WithOriginal, Meta>); - impl FieldExportOps { - pub fn hint(&self, ty: &Type) -> Result<(TokenStream, TokenStream), TokenStream> { + pub fn to_export_meta(&self, ty: &Type, span: Span) -> Result { let godot_types = godot_types(); let property_hints = quote!(#godot_types::global::PropertyHint); + let property_usage = quote!(#godot_types::global::PropertyUsageFlags); + let default_usage = quote!(#property_usage::SCRIPT_VARIABLE | #property_usage::EDITOR | #property_usage::STORAGE); - let mut result: Option<(&str, TokenStream, TokenStream)> = None; + let mut result: Option = None; if let Some(color_no_alpha) = self.color_no_alpha.as_ref() { - result = Some(( - "color_no_alpha", - quote_spanned!(color_no_alpha.original.span() => #property_hints::COLOR_NO_ALPHA), - quote_spanned!(color_no_alpha.original.span() => String::new()), - )); + result = Some(ExportMetadata { + field: "color_no_alpha", + usage: quote_spanned!(color_no_alpha.original.span() => #default_usage), + hint: quote_spanned!(color_no_alpha.original.span() => #property_hints::COLOR_NO_ALPHA), + hint_string: quote_spanned!(color_no_alpha.original.span() => String::new()), + }); } if let Some(dir) = self.dir.as_ref() { - let field = "dir"; + const FIELD: &str = "dir"; - if let Some((active_field, _, _)) = result { - return Self::error(dir.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(dir.original.span(), active_meta.field, FIELD); } - result = Some(( - field, - quote_spanned!(dir.original.span() => Some(#property_hints::DIR)), - quote_spanned!(dir.original.span() => Some(String::new())), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(dir.original.span() => #default_usage), + hint: quote_spanned!(dir.original.span() => Some(#property_hints::DIR)), + hint_string: quote_spanned!(dir.original.span() => Some(String::new())), + }); } if let Some(exp_list) = self.exp_easing.as_ref() { - let field = "exp_easing"; + const FIELD: &str = "exp_easing"; - if let Some((active_field, _, _)) = result { - return Self::error(exp_list.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(exp_list.original.span(), active_meta.field, FIELD); } let parsed_params = exp_list @@ -89,18 +95,19 @@ impl FieldExportOps { .collect::>() .join(","); - result = Some(( - field, - quote_spanned!(exp_list.original.span() => Some(#property_hints::EXP_EASING)), - quote_spanned!(exp_list.original.span() => Some(String::from(#serialized_params))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(exp_list.original.span() => #default_usage), + hint: quote_spanned!(exp_list.original.span() => Some(#property_hints::EXP_EASING)), + hint_string: quote_spanned!(exp_list.original.span() => Some(String::from(#serialized_params))), + }); } if let Some(list) = self.file.as_ref() { - let field = "file"; + const FIELD: &str = "file"; - if let Some((active_field, _, _)) = result { - return Self::error(list.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(list.original.span(), active_meta.field, FIELD); } let filters = list @@ -112,18 +119,19 @@ impl FieldExportOps { .map_err(|err| err.write_errors())? .join(","); - result = Some(( - field, - quote_spanned!(list.original.span() => Some(#property_hints::FILE)), - quote_spanned!(list.original.span() => Some(String::from(#filters))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(list.original.span() => #default_usage), + hint: quote_spanned!(list.original.span() => Some(#property_hints::FILE)), + hint_string: quote_spanned!(list.original.span() => Some(String::from(#filters))), + }); } if let Some(list) = self.enum_options.as_ref() { - let field = "enum_options"; + const FIELD: &str = "enum_options"; - if let Some((active_field, _, _)) = result { - return Self::error(list.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(list.original.span(), active_meta.field, FIELD); } let flags = list @@ -135,18 +143,19 @@ impl FieldExportOps { .map_err(|err| err.write_errors())? .join(","); - result = Some(( - field, - quote_spanned!(list.original.span() => Some(#property_hints::ENUM)), - quote_spanned!(list.original.span() => Some(String::from(#flags))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(list.original.span() => #default_usage), + hint: quote_spanned!(list.original.span() => Some(#property_hints::ENUM)), + hint_string: quote_spanned!(list.original.span() => Some(String::from(#flags))), + }); } if let Some(list) = self.flags.as_ref() { - let field = "flags"; + const FIELD: &str = "flags"; - if let Some((active_field, _, _)) = result { - return Self::error(list.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(list.original.span(), active_meta.field, FIELD); } let flags = list @@ -158,60 +167,64 @@ impl FieldExportOps { .map_err(|err| err.write_errors())? .join(","); - result = Some(( - field, - quote_spanned!(list.original.span() => Some(#property_hints::FLAGS)), - quote_spanned!(list.original.span() => Some(String::from(#flags))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(list.original.span() => #default_usage), + hint: quote_spanned!(list.original.span() => Some(#property_hints::FLAGS)), + hint_string: quote_spanned!(list.original.span() => Some(String::from(#flags))), + }); } if let Some(global_dir) = self.global_dir.as_ref() { - let field = "global_dir"; + const FIELD: &str = "global_dir"; - if let Some((active_field, _, _)) = result { - return Self::error(global_dir.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(global_dir.original.span(), active_meta.field, FIELD); } - result = Some(( - field, - quote_spanned!(global_dir.original.span() => Some(#property_hints::GLOBAL_DIR)), - quote_spanned!(global_dir.original.span() => Some(String::new())), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(global_dir.original.span() => #default_usage), + hint: quote_spanned!(global_dir.original.span() => Some(#property_hints::GLOBAL_DIR)), + hint_string: quote_spanned!(global_dir.original.span() => Some(String::new())), + }); } if let Some(global_file) = self.global_file.as_ref() { - let field = "global_file"; + const FIELD: &str = "global_file"; - if let Some((active_field, _, _)) = result { - return Self::error(global_file.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(global_file.original.span(), active_meta.field, FIELD); } - result = Some(( - field, - quote_spanned!(global_file.original.span() => Some(#property_hints::GLOBAL_FILE)), - quote_spanned!(global_file.original.span() => Some(String::new())), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(global_file.original.span() => #default_usage), + hint: quote_spanned!(global_file.original.span() => Some(#property_hints::GLOBAL_FILE)), + hint_string: quote_spanned!(global_file.original.span() => Some(String::new())), + }); } if let Some(multiline) = self.multiline.as_ref() { - let field = "multiline"; + const FIELD: &str = "multiline"; - if let Some((active_field, _, _)) = result { - return Self::error(multiline.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(multiline.original.span(), active_meta.field, FIELD); } - result = Some(( - field, - quote_spanned!(multiline.original.span() => Some(#property_hints::MULTILINE)), - quote_spanned!(multiline.original.span() => Some(String::new())), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(multiline.original.span() => #default_usage), + hint: quote_spanned!(multiline.original.span() => Some(#property_hints::MULTILINE)), + hint_string: quote_spanned!(multiline.original.span() => Some(String::new())), + }); } if let Some(list) = self.node_path.as_ref() { - let field = "node_path"; + const FIELD: &str = "node_path"; - if let Some((active_field, _, _)) = result { - return Self::error(list.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(list.original.span(), active_meta.field, FIELD); } let types = list @@ -223,77 +236,142 @@ impl FieldExportOps { .map_err(|err| err.write_errors())? .join(","); - result = Some(( - field, - quote_spanned!(list.original.span() => Some(#property_hints::NODE_PATH_VALID_TYPES)), - quote_spanned!(list.original.span() => Some(String::from(#types))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(list.original.span() => #default_usage), + hint: quote_spanned!(list.original.span() => Some(#property_hints::NODE_PATH_VALID_TYPES)), + hint_string: quote_spanned!(list.original.span() => Some(String::from(#types))), + }); } if let Some(text) = self.placeholder.as_ref() { - let field = "placeholder"; + const FIELD: &str = "placeholder"; - if let Some((active_field, _, _)) = result { - return Self::error(text.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(text.original.span(), active_meta.field, FIELD); } let content = &text.parsed; - result = Some(( - field, - quote_spanned!(text.original.span() => Some(#property_hints::PLACEHOLDER_TEXT)), - quote_spanned!(text.original.span() => Some(String::from(#content))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(text.original.span() => #default_usage), + hint: quote_spanned!(text.original.span() => Some(#property_hints::PLACEHOLDER_TEXT)), + hint_string: quote_spanned!(text.original.span() => Some(String::from(#content))), + }); } if let Some(ops) = self.range.as_ref() { - let field = "range"; + const FIELD: &str = "range"; - if let Some((active_field, _, _)) = result { - return Self::error(ops.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(ops.original.span(), active_meta.field, FIELD); } let step = ops.parsed.step.unwrap_or(1.0); let hint_string = format!("{},{},{}", ops.parsed.min, ops.parsed.max, step); - result = Some(( - field, - quote_spanned!(ops.original.span() => Some(#property_hints::RANGE)), - quote_spanned!(ops.original.span() => Some(String::from(#hint_string))), - )); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(ops.original.span() => #default_usage), + hint: quote_spanned!(ops.original.span() => Some(#property_hints::RANGE)), + hint_string: quote_spanned!(ops.original.span() => Some(String::from(#hint_string))), + }); } if let Some(attr_ty) = self.custom_type.as_ref() { - let field = "ty"; + const FIELD: &str = "ty"; - if let Some((active_field, _, _)) = result { - return Self::error(attr_ty.original.span(), active_field, field); + if let Some(active_meta) = result { + return Self::error(attr_ty.original.span(), active_meta.field, FIELD); } let attr_ty_raw = &attr_ty.parsed; - let hint = quote_spanned!(ty.span() => None); + let hint = quote_spanned!(attr_ty.original.span() => None); let hint_string = quote_spanned!(attr_ty.original.span() => Some(String::from(#attr_ty_raw))); - result = Some((field, hint, hint_string)); + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(attr_ty.original.span() => #default_usage), + hint, + hint_string, + }); + } + + if let Some(attr_storage) = self.storage.as_ref() { + const FIELD: &str = "storage"; + + if let Some(active_meta) = result { + return Self::error(attr_storage.original.span(), active_meta.field, FIELD); + } + + if !attr_storage.parsed { + let err = syn::Error::new_spanned( + &attr_storage.original, + "storage can not be set to false", + ) + .into_compile_error(); + + return Err(err); + } + + let hint = quote_spanned!(attr_storage.original.span() => None); + let hint_string = quote_spanned!(attr_storage.original.span() => None); + + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(attr_storage.original.span() => #property_usage::SCRIPT_VARIABLE | #property_usage::STORAGE), + hint, + hint_string, + }); + } + + if let Some(attr_custom) = self.custom.as_ref() { + const FIELD: &str = "custom"; + + if let Some(active_meta) = result { + return Self::error(attr_custom.original.span(), active_meta.field, FIELD); + } + + let attr_hint = &attr_custom.parsed.hint; + let attr_hint_str = &attr_custom.parsed.hint_string; + + let hint = quote_spanned!(attr_custom.original.span() => Some({ let hint: #property_hints = #attr_hint; hint })); + let hint_string = + quote_spanned!(attr_custom.original.span() => Some(String::from(#attr_hint_str))); + + result = Some(ExportMetadata { + field: FIELD, + usage: quote_spanned!(attr_custom.original.span() => #default_usage), + hint, + hint_string, + }); } - let (hint, hint_string) = result - .map(|(_, tokens, hint_string)| (tokens, hint_string)) - .unwrap_or_else(|| (quote!(None), quote!(None))); + let metadata = result.unwrap_or_else(|| ExportMetadata { + field: "", + usage: quote_spanned!(span => #default_usage), + hint: quote_spanned!(span => None), + hint_string: quote_spanned!(span => None), + }); + + let hint = &metadata.hint; + let hint_string = &metadata.hint_string; let default_hint = quote_spanned!(ty.span() => <#ty as ::godot_rust_script::GodotScriptExport>::hint(#hint)); let default_hint_string = quote_spanned!(ty.span() => <#ty as ::godot_rust_script::GodotScriptExport>::hint_string(#hint, #hint_string)); - Ok((default_hint, default_hint_string)) + Ok(ExportMetadata { + field: metadata.field, + usage: metadata.usage, + hint: default_hint, + hint_string: default_hint_string, + }) } - fn error( - span: Span, - active_field: &str, - field: &str, - ) -> Result<(TokenStream, TokenStream), TokenStream> { + fn error(span: Span, active_field: &str, field: &str) -> Result { let err = syn::Error::new( span, format!("{} is not compatible with {}", field, active_field), @@ -317,6 +395,12 @@ enum ExpEasingOpts { PositiveOnly, } +#[derive(FromMeta, Debug)] +struct ExportCustomOps { + hint: PatPath, + hint_string: String, +} + #[derive(FromField, Debug)] #[darling(forward_attrs(export, prop, doc, signal))] pub struct FieldOpts { @@ -341,3 +425,10 @@ pub struct PropertyOpts { pub get: Option, pub set: Option, } + +pub struct ExportMetadata { + pub field: &'static str, + pub usage: TokenStream, + pub hint: TokenStream, + pub hint_string: TokenStream, +} diff --git a/derive/src/impl_attribute.rs b/derive/src/impl_attribute.rs index 65229d4..68e9438 100644 --- a/derive/src/impl_attribute.rs +++ b/derive/src/impl_attribute.rs @@ -74,7 +74,7 @@ pub fn godot_script_impl( name: stringify!(#arg_name), ty: #arg_type, class_name: <<#arg_rust_type as #godot_types::meta::GodotConvert>::Via as #godot_types::meta::GodotType>::class_id(), - exported: false, + usage: #godot_types::global::PropertyUsageFlags::NONE, hint: #property_hints::NONE, hint_string: String::new(), description: "", @@ -135,7 +135,7 @@ pub fn godot_script_impl( name: #fn_name_str, ty: #fn_return_ty, class_name: <<#fn_return_ty_rust as #godot_types::meta::GodotConvert>::Via as #godot_types::meta::GodotType>::class_id(), - exported: false, + usage: #godot_types::global::PropertyUsageFlags::NONE, hint: #property_hints::NONE, hint_string: String::new(), description: "", diff --git a/derive/src/lib.rs b/derive/src/lib.rs index f47cf92..9ebb14c 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -9,15 +9,16 @@ mod enums; mod impl_attribute; mod type_paths; -use attribute_ops::{FieldOpts, GodotScriptOpts}; use darling::{util::SpannedValue, FromAttributes, FromDeriveInput, FromMeta}; use itertools::Itertools; 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, property_hints, string_name_ty, variant_ty}; -use crate::attribute_ops::{FieldExportOps, FieldSignalOps, PropertyOpts}; +use crate::attribute_ops::{ + ExportMetadata, FieldExportOps, FieldOpts, FieldSignalOps, GodotScriptOpts, PropertyOpts, +}; +use crate::type_paths::{godot_types, property_hints, property_usage, string_name_ty, variant_ty}; #[proc_macro_derive(GodotScript, attributes(export, script, prop, signal))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -383,6 +384,7 @@ fn derive_field_metadata( ) -> Result { let godot_types = godot_types(); let property_hint_ty = property_hints(); + let property_usage_ty = property_usage(); let name = field .ident .as_ref() @@ -392,19 +394,30 @@ fn derive_field_metadata( let rust_ty = &field.ty; let ty = rust_to_variant_type(&field.ty)?; - let (hint, hint_string) = is_exported + let ExportMetadata { + field: _, + usage, + hint, + hint_string, + } = is_exported .then(|| { let ops = FieldExportOps::from_attributes(&field.attrs).map_err(|err| err.write_errors())?; + let span = field + .attrs + .iter() + .find(|attr| attr.path().is_ident("export")) + .expect("FieldExportOps already succeded") + .span(); - ops.hint(&field.ty) + ops.to_export_meta(&field.ty, span) }) .transpose()? - .unwrap_or_else(|| { - ( - quote_spanned!(field.span()=> #property_hint_ty::NONE), - quote_spanned!(field.span()=> String::new()), - ) + .unwrap_or_else(|| ExportMetadata { + field: "", + usage: quote_spanned!(field.span() => #property_usage_ty::SCRIPT_VARIABLE), + hint: quote_spanned!(field.span()=> #property_hint_ty::NONE), + hint_string: quote_spanned!(field.span()=> String::new()), }); let description = get_field_description(field); @@ -413,7 +426,7 @@ fn derive_field_metadata( name: #name, ty: #ty, class_name: <<#rust_ty as #godot_types::meta::GodotConvert>::Via as #godot_types::meta::GodotType>::class_id(), - exported: #is_exported, + usage: #usage, hint: #hint, hint_string: #hint_string, description: concat!(#description), @@ -530,7 +543,7 @@ fn extract_ident_from_type(impl_target: &syn::Type) -> Result Err(compile_error("Macro types are not supported!", impl_target)), Type::Never(_) => Err(compile_error("Never type is not supported!", impl_target)), Type::Paren(_) => Err(compile_error("Unsupported type!", impl_target)), - Type::Path(ref path) => Ok(path.path.segments.last().unwrap().ident.clone()), + Type::Path(path) => Ok(path.path.segments.last().unwrap().ident.clone()), Type::Ptr(_) => Err(compile_error( "Pointer types are not supported!", impl_target, diff --git a/derive/src/type_paths.rs b/derive/src/type_paths.rs index f827489..344ddee 100644 --- a/derive/src/type_paths.rs +++ b/derive/src/type_paths.rs @@ -7,28 +7,40 @@ use proc_macro2::TokenStream; use quote::quote; +#[inline] pub fn godot_types() -> TokenStream { quote!(::godot_rust_script::godot) } +#[inline] pub fn property_hints() -> TokenStream { let godot_types = godot_types(); quote!(#godot_types::global::PropertyHint) } +#[inline] +pub fn property_usage() -> TokenStream { + let godot_types = godot_types(); + + quote!(#godot_types::global::PropertyUsageFlags) +} + +#[inline] pub fn variant_ty() -> TokenStream { let godot_types = godot_types(); quote!(#godot_types::prelude::Variant) } +#[inline] pub fn string_name_ty() -> TokenStream { let godot_types = godot_types(); quote!(#godot_types::prelude::StringName) } +#[inline] pub fn convert_error_ty() -> TokenStream { let godot_types = godot_types(); diff --git a/rust-script/src/interface/signals.rs b/rust-script/src/interface/signals.rs index b76ab56..f14dd4e 100644 --- a/rust-script/src/interface/signals.rs +++ b/rust-script/src/interface/signals.rs @@ -10,7 +10,7 @@ use godot::builtin::{ Callable, Dictionary, GString, NodePath, StringName, Variant, Vector2, Vector3, Vector4, }; use godot::classes::Object; -use godot::global::{Error, PropertyHint}; +use godot::global::{Error, PropertyHint, PropertyUsageFlags}; use godot::meta::{ByValue, GodotConvert, GodotType, ToGodot}; use godot::obj::{Gd, GodotClass}; @@ -113,7 +113,7 @@ macro_rules! signal_argument_desc { name: $name, ty: <<<$type as GodotConvert>::Via as GodotType>::Ffi as godot::sys::GodotFfi>::VARIANT_TYPE.variant_as_nil(), class_name: <<$type as GodotConvert>::Via as GodotType>::class_id(), - exported: false, + usage: PropertyUsageFlags::NONE, hint: PropertyHint::NONE, hint_string: String::new(), description: "", diff --git a/rust-script/src/static_script_registry.rs b/rust-script/src/static_script_registry.rs index f1bf74f..e785b4d 100644 --- a/rust-script/src/static_script_registry.rs +++ b/rust-script/src/static_script_registry.rs @@ -84,7 +84,7 @@ pub struct RustScriptPropDesc { pub name: &'static str, pub ty: VariantType, pub class_name: ClassId, - pub exported: bool, + pub usage: PropertyUsageFlags, pub hint: PropertyHint, pub hint_string: String, pub description: &'static str, @@ -96,11 +96,7 @@ impl RustScriptPropDesc { variant_type: self.ty, class_name: self.class_name, property_name: self.name, - usage: if self.exported { - (PropertyUsageFlags::EDITOR | PropertyUsageFlags::STORAGE).ord() - } else { - PropertyUsageFlags::NONE.ord() - }, + usage: self.usage.ord(), hint: self.hint.ord(), hint_string: self.hint_string.clone(), description: self.description, diff --git a/rust-script/tests/script_derive.rs b/rust-script/tests/script_derive.rs index 8215fe1..44ec1a3 100644 --- a/rust-script/tests/script_derive.rs +++ b/rust-script/tests/script_derive.rs @@ -6,6 +6,7 @@ use godot::builtin::{Array, GString}; use godot::classes::{Node, Node3D}; +use godot::global::PropertyHint; use godot::obj::{Gd, NewAlloc}; use godot_rust_script::{ godot_script_impl, CastToScript, Context, GodotScript, GodotScriptEnum, OnEditor, RsRef, @@ -58,14 +59,15 @@ struct TestScript { #[export(range(min = 0.0, max = 10.0))] pub int_range: u32, - #[export] + #[export(storage)] pub custom_enum: ScriptEnum, #[export] pub script_ref_opt: Option>, - #[export] + #[export(custom(hint = PropertyHint::NODE_TYPE, hint_string = ""))] pub script_ref: OnEditor>, + base: Gd<::Base>, }