From 76b77732225494fad5bd9e067b805bcc5d684637 Mon Sep 17 00:00:00 2001 From: bogay Date: Sun, 15 Oct 2023 02:29:24 +0800 Subject: [PATCH] feat(Export): add #[export(...)] attribute --- gdnative-derive/src/export.rs | 107 ++++++++++++++++++++---- gdnative-derive/src/lib.rs | 5 +- gdnative/tests/ui/export_fail_01.rs | 1 + gdnative/tests/ui/export_fail_01.stderr | 8 +- gdnative/tests/ui/export_fail_02.rs | 1 + gdnative/tests/ui/export_fail_02.stderr | 4 +- gdnative/tests/ui/export_fail_03.rs | 1 + gdnative/tests/ui/export_fail_03.stderr | 8 +- gdnative/tests/ui/export_pass.rs | 1 + 9 files changed, 110 insertions(+), 26 deletions(-) diff --git a/gdnative-derive/src/export.rs b/gdnative-derive/src/export.rs index 4b1727f1c..7862c330c 100644 --- a/gdnative-derive/src/export.rs +++ b/gdnative-derive/src/export.rs @@ -1,25 +1,104 @@ use crate::crate_gdnative_core; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::ToTokens; use syn::spanned::Spanned; -use syn::{DeriveInput, Fields}; +use syn::{DeriveInput, Fields, Meta}; + +#[derive(Copy, Clone, Debug)] +enum Kind { + Enum, +} + +#[derive(Debug)] +struct DeriveData { + kind: Kind, + ident: Ident, + data: syn::Data, +} + +fn parse_derive_input(input: DeriveInput) -> syn::Result { + let DeriveInput { + ident, data, attrs, .. + } = input.clone(); + + let (kind, errors) = attrs + .iter() + .fold((None, vec![]), |(mut kind, mut errors), attr| { + if attr.path.is_ident("export") { + if let Ok(Meta::List(list)) = attr.parse_meta() { + for meta in list.nested.into_iter() { + if let syn::NestedMeta::Meta(Meta::NameValue(pair)) = meta { + if !pair.path.is_ident("kind") { + errors.push(syn::Error::new( + pair.span(), + format!( + "Found {}, expected kind", + pair.path.into_token_stream() + ), + )) + } else if let syn::Lit::Str(str) = pair.lit { + if "enum" == str.value() { + if kind.is_some() { + errors + .push(syn::Error::new(str.span(), "kind already set")); + } else { + kind = Some(Kind::Enum); + } + } else { + errors.push(syn::Error::new( + str.span(), + format!("Found {}, expected enum", str.value()), + )); + } + } else { + errors.push(syn::Error::new( + pair.lit.span(), + "Expected a string literal", + )) + } + } + } + } + } + + (kind, errors) + }); + + if let Some(err) = errors.into_iter().reduce(|mut acc, err| { + acc.combine(err); + acc + }) { + return Err(err); + } + + match kind { + Some(kind) => Ok(DeriveData { ident, kind, data }), + None => Err(syn::Error::new(Span::call_site(), "kind not found")), + } +} fn err_only_supports_fieldless_enums(span: Span) -> syn::Error { syn::Error::new(span, "#[derive(Export)] only supports fieldless enums") } -pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result { - let derived_enum = match &input.data { - syn::Data::Enum(data) => data, - syn::Data::Struct(data) => { - return Err(err_only_supports_fieldless_enums(data.struct_token.span())); - } - syn::Data::Union(data) => { - return Err(err_only_supports_fieldless_enums(data.union_token.span())); - } - }; +pub(crate) fn derive_export(input: DeriveInput) -> syn::Result { + let derive_data = parse_derive_input(input)?; - let export_impl = impl_export(&input.ident, derived_enum)?; - Ok(export_impl) + match derive_data.kind { + Kind::Enum => { + let derived_enum = match derive_data.data { + syn::Data::Enum(data) => data, + syn::Data::Struct(data) => { + return Err(err_only_supports_fieldless_enums(data.struct_token.span())); + } + syn::Data::Union(data) => { + return Err(err_only_supports_fieldless_enums(data.union_token.span())); + } + }; + let export_impl = impl_export(&derive_data.ident, &derived_enum)?; + Ok(export_impl) + } + } } fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result { diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 88f1f6b04..2d4138879 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -676,6 +676,7 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// /// #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)] /// #[variant(enum = "repr")] +/// #[export(kind = "enum")] /// #[repr(i32)] /// enum Dir { /// Up = 1, @@ -712,10 +713,10 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream { /// f1: i32 /// } /// ``` -#[proc_macro_derive(Export)] +#[proc_macro_derive(Export, attributes(export))] pub fn derive_export(input: TokenStream) -> TokenStream { let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); - match export::derive_export(&derive_input) { + match export::derive_export(derive_input) { Ok(stream) => stream.into(), Err(err) => err.to_compile_error().into(), } diff --git a/gdnative/tests/ui/export_fail_01.rs b/gdnative/tests/ui/export_fail_01.rs index c69fed7d3..0a2ea147a 100644 --- a/gdnative/tests/ui/export_fail_01.rs +++ b/gdnative/tests/ui/export_fail_01.rs @@ -1,6 +1,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] +#[export(kind = "enum")] pub enum Foo { Bar(String), Baz { a: i32, b: u32 }, diff --git a/gdnative/tests/ui/export_fail_01.stderr b/gdnative/tests/ui/export_fail_01.stderr index 79cc55485..2848a43c4 100644 --- a/gdnative/tests/ui/export_fail_01.stderr +++ b/gdnative/tests/ui/export_fail_01.stderr @@ -1,11 +1,11 @@ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_01.rs:5:5 + --> tests/ui/export_fail_01.rs:6:5 | -5 | Bar(String), +6 | Bar(String), | ^^^ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_01.rs:6:5 + --> tests/ui/export_fail_01.rs:7:5 | -6 | Baz { a: i32, b: u32 }, +7 | Baz { a: i32, b: u32 }, | ^^^ diff --git a/gdnative/tests/ui/export_fail_02.rs b/gdnative/tests/ui/export_fail_02.rs index 7ce4c080e..fa1c82498 100644 --- a/gdnative/tests/ui/export_fail_02.rs +++ b/gdnative/tests/ui/export_fail_02.rs @@ -1,6 +1,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] +#[export(kind = "enum")] pub struct Foo { bar: i32, } diff --git a/gdnative/tests/ui/export_fail_02.stderr b/gdnative/tests/ui/export_fail_02.stderr index 288715542..64b5e1656 100644 --- a/gdnative/tests/ui/export_fail_02.stderr +++ b/gdnative/tests/ui/export_fail_02.stderr @@ -1,5 +1,5 @@ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_02.rs:4:5 + --> tests/ui/export_fail_02.rs:5:5 | -4 | pub struct Foo { +5 | pub struct Foo { | ^^^^^^ diff --git a/gdnative/tests/ui/export_fail_03.rs b/gdnative/tests/ui/export_fail_03.rs index d89f24118..4641ffe2e 100644 --- a/gdnative/tests/ui/export_fail_03.rs +++ b/gdnative/tests/ui/export_fail_03.rs @@ -1,6 +1,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant)] +#[export(kind = "enum")] pub union Foo { bar: i32, } diff --git a/gdnative/tests/ui/export_fail_03.stderr b/gdnative/tests/ui/export_fail_03.stderr index 304eff3b3..b9184f792 100644 --- a/gdnative/tests/ui/export_fail_03.stderr +++ b/gdnative/tests/ui/export_fail_03.stderr @@ -1,11 +1,11 @@ error: #[derive(Export)] only supports fieldless enums - --> tests/ui/export_fail_03.rs:4:5 + --> tests/ui/export_fail_03.rs:5:5 | -4 | pub union Foo { +5 | pub union Foo { | ^^^^^ error: Variant conversion derive macro does not work on unions. --> tests/ui/export_fail_03.rs:4:1 | -4 | pub union Foo { - | ^^^ +4 | #[export(kind = "enum")] + | ^ diff --git a/gdnative/tests/ui/export_pass.rs b/gdnative/tests/ui/export_pass.rs index a5b12b5de..d417a105a 100644 --- a/gdnative/tests/ui/export_pass.rs +++ b/gdnative/tests/ui/export_pass.rs @@ -2,6 +2,7 @@ use gdnative::prelude::*; #[derive(Export, ToVariant, Clone, Copy)] #[variant(enum = "repr")] +#[export(kind = "enum")] #[repr(i32)] pub enum Foo { Bar,