Skip to content

Commit 7159dd9

Browse files
committed
feat(Export): add #[export(...)] attribute
1 parent e055ea2 commit 7159dd9

22 files changed

+255
-26
lines changed

gdnative-derive/src/export.rs

Lines changed: 123 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,134 @@
11
use crate::crate_gdnative_core;
2-
use proc_macro2::{Span, TokenStream as TokenStream2};
2+
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
3+
use quote::ToTokens;
34
use syn::spanned::Spanned;
4-
use syn::{DeriveInput, Fields};
5+
use syn::{DeriveInput, Fields, Meta};
6+
7+
#[derive(Copy, Clone, Debug)]
8+
enum Kind {
9+
Enum,
10+
}
11+
12+
#[derive(Debug)]
13+
struct DeriveData {
14+
kind: Kind,
15+
ident: Ident,
16+
data: syn::Data,
17+
}
18+
19+
fn parse_derive_input(input: DeriveInput) -> syn::Result<DeriveData> {
20+
let DeriveInput {
21+
ident, data, attrs, ..
22+
} = input.clone();
23+
24+
let (kind, errors) = attrs
25+
.iter()
26+
.filter(|attr| attr.path.is_ident("export"))
27+
.fold((None, vec![]), |(mut kind, mut errors), attr| {
28+
let list = match attr.parse_meta() {
29+
Ok(meta) => match meta {
30+
Meta::List(list) => list,
31+
Meta::Path(path) => {
32+
errors.push(syn::Error::new(
33+
path.span(),
34+
"missing macro arguments. expected #[export(...)]",
35+
));
36+
return (kind, errors);
37+
}
38+
Meta::NameValue(pair) => {
39+
errors.push(syn::Error::new(
40+
pair.span(),
41+
"missing macro arguments. expected #[export(...)]",
42+
));
43+
return (kind, errors);
44+
}
45+
},
46+
Err(e) => {
47+
errors.push(syn::Error::new(
48+
e.span(),
49+
format!("unknown attribute format. expected #[export(...)]: {e}"),
50+
));
51+
return (kind, errors);
52+
}
53+
};
54+
55+
for meta in list.nested.into_iter() {
56+
let syn::NestedMeta::Meta(Meta::NameValue(pair)) = meta else {
57+
errors.push(syn::Error::new(
58+
meta.span(),
59+
"invalid syntax. expected #[export(key = \"value\")]",
60+
));
61+
continue;
62+
};
63+
64+
if !pair.path.is_ident("kind") {
65+
errors.push(syn::Error::new(
66+
pair.span(),
67+
format!("found {}, expected kind", pair.path.into_token_stream()),
68+
));
69+
continue;
70+
}
71+
72+
let syn::Lit::Str(str) = pair.lit else {
73+
errors.push(syn::Error::new(
74+
pair.lit.span(),
75+
"string literal expected, wrap with double quotes",
76+
));
77+
continue;
78+
};
79+
80+
match str.value().as_str() {
81+
"enum" => {
82+
if kind.is_some() {
83+
errors.push(syn::Error::new(str.span(), "kind already set"));
84+
} else {
85+
kind = Some(Kind::Enum);
86+
}
87+
}
88+
_ => {
89+
errors.push(syn::Error::new(str.span(), "unknown kind, expected enum"));
90+
}
91+
}
92+
}
93+
94+
(kind, errors)
95+
});
96+
97+
if let Some(err) = errors.into_iter().reduce(|mut acc, err| {
98+
acc.combine(err);
99+
acc
100+
}) {
101+
return Err(err);
102+
}
103+
104+
match kind {
105+
Some(kind) => Ok(DeriveData { ident, kind, data }),
106+
None => Err(syn::Error::new(Span::call_site(), "kind not found")),
107+
}
108+
}
5109

6110
fn err_only_supports_fieldless_enums(span: Span) -> syn::Error {
7111
syn::Error::new(span, "#[derive(Export)] only supports fieldless enums")
8112
}
9113

10-
pub(crate) fn derive_export(input: &DeriveInput) -> syn::Result<TokenStream2> {
11-
let derived_enum = match &input.data {
12-
syn::Data::Enum(data) => data,
13-
syn::Data::Struct(data) => {
14-
return Err(err_only_supports_fieldless_enums(data.struct_token.span()));
15-
}
16-
syn::Data::Union(data) => {
17-
return Err(err_only_supports_fieldless_enums(data.union_token.span()));
18-
}
19-
};
114+
pub(crate) fn derive_export(input: DeriveInput) -> syn::Result<TokenStream2> {
115+
let derive_data = parse_derive_input(input)?;
20116

21-
let export_impl = impl_export(&input.ident, derived_enum)?;
22-
Ok(export_impl)
117+
match derive_data.kind {
118+
Kind::Enum => {
119+
let derived_enum = match derive_data.data {
120+
syn::Data::Enum(data) => data,
121+
syn::Data::Struct(data) => {
122+
return Err(err_only_supports_fieldless_enums(data.struct_token.span()));
123+
}
124+
syn::Data::Union(data) => {
125+
return Err(err_only_supports_fieldless_enums(data.union_token.span()));
126+
}
127+
};
128+
let export_impl = impl_export(&derive_data.ident, &derived_enum)?;
129+
Ok(export_impl)
130+
}
131+
}
23132
}
24133

25134
fn impl_export(enum_ty: &syn::Ident, data: &syn::DataEnum) -> syn::Result<TokenStream2> {

gdnative-derive/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream {
676676
///
677677
/// #[derive(Debug, PartialEq, Clone, Copy, Export, ToVariant, FromVariant)]
678678
/// #[variant(enum = "repr")]
679+
/// #[export(kind = "enum")]
679680
/// #[repr(i32)]
680681
/// enum Dir {
681682
/// Up = 1,
@@ -712,10 +713,10 @@ pub fn godot_wrap_method(input: TokenStream) -> TokenStream {
712713
/// f1: i32
713714
/// }
714715
/// ```
715-
#[proc_macro_derive(Export)]
716+
#[proc_macro_derive(Export, attributes(export))]
716717
pub fn derive_export(input: TokenStream) -> TokenStream {
717718
let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
718-
match export::derive_export(&derive_input) {
719+
match export::derive_export(derive_input) {
719720
Ok(stream) => stream.into(),
720721
Err(err) => err.to_compile_error().into(),
721722
}

gdnative/tests/ui.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ fn ui_tests() {
4646
t.compile_fail("tests/ui/export_fail_01.rs");
4747
t.compile_fail("tests/ui/export_fail_02.rs");
4848
t.compile_fail("tests/ui/export_fail_03.rs");
49+
t.compile_fail("tests/ui/export_fail_04.rs");
50+
t.compile_fail("tests/ui/export_fail_05.rs");
51+
t.compile_fail("tests/ui/export_fail_06.rs");
52+
t.compile_fail("tests/ui/export_fail_07.rs");
53+
t.compile_fail("tests/ui/export_fail_08.rs");
54+
t.compile_fail("tests/ui/export_fail_09.rs");
4955
}
5056

5157
// FIXME(rust/issues/54725): Full path spans are only available on nightly as of now

gdnative/tests/ui/export_fail_01.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use gdnative::prelude::*;
22

33
#[derive(Export, ToVariant)]
4+
#[export(kind = "enum")]
45
pub enum Foo {
56
Bar(String),
67
Baz { a: i32, b: u32 },
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error: #[derive(Export)] only supports fieldless enums
2-
--> tests/ui/export_fail_01.rs:5:5
2+
--> tests/ui/export_fail_01.rs:6:5
33
|
4-
5 | Bar(String),
4+
6 | Bar(String),
55
| ^^^
66

77
error: #[derive(Export)] only supports fieldless enums
8-
--> tests/ui/export_fail_01.rs:6:5
8+
--> tests/ui/export_fail_01.rs:7:5
99
|
10-
6 | Baz { a: i32, b: u32 },
10+
7 | Baz { a: i32, b: u32 },
1111
| ^^^

gdnative/tests/ui/export_fail_02.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use gdnative::prelude::*;
22

33
#[derive(Export, ToVariant)]
4+
#[export(kind = "enum")]
45
pub struct Foo {
56
bar: i32,
67
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: #[derive(Export)] only supports fieldless enums
2-
--> tests/ui/export_fail_02.rs:4:5
2+
--> tests/ui/export_fail_02.rs:5:5
33
|
4-
4 | pub struct Foo {
4+
5 | pub struct Foo {
55
| ^^^^^^

gdnative/tests/ui/export_fail_03.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use gdnative::prelude::*;
22

33
#[derive(Export, ToVariant)]
4+
#[export(kind = "enum")]
45
pub union Foo {
56
bar: i32,
67
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error: #[derive(Export)] only supports fieldless enums
2-
--> tests/ui/export_fail_03.rs:4:5
2+
--> tests/ui/export_fail_03.rs:5:5
33
|
4-
4 | pub union Foo {
4+
5 | pub union Foo {
55
| ^^^^^
66

77
error: Variant conversion derive macro does not work on unions.
88
--> tests/ui/export_fail_03.rs:4:1
99
|
10-
4 | pub union Foo {
11-
| ^^^
10+
4 | #[export(kind = "enum")]
11+
| ^

gdnative/tests/ui/export_fail_04.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use gdnative::prelude::*;
2+
3+
#[derive(Export, ToVariant)]
4+
#[export]
5+
pub enum Foo {
6+
Bar,
7+
}
8+
9+
#[derive(Export, ToVariant)]
10+
#[export = "foo"]
11+
pub enum Bar {
12+
Foo,
13+
}
14+
15+
#[derive(Export, ToVariant)]
16+
#[export(weird format a => b)]
17+
pub enum Baz {
18+
Quux,
19+
}
20+
21+
fn main() {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: missing macro arguments. expected #[export(...)]
2+
--> tests/ui/export_fail_04.rs:4:3
3+
|
4+
4 | #[export]
5+
| ^^^^^^
6+
7+
error: missing macro arguments. expected #[export(...)]
8+
--> tests/ui/export_fail_04.rs:10:3
9+
|
10+
10 | #[export = "foo"]
11+
| ^^^^^^
12+
13+
error: unknown attribute format. expected #[export(...)]: expected `,`
14+
--> tests/ui/export_fail_04.rs:16:16
15+
|
16+
16 | #[export(weird format a => b)]
17+
| ^^^^^^

gdnative/tests/ui/export_fail_05.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use gdnative::prelude::*;
2+
3+
#[derive(Export, ToVariant)]
4+
#[export(kind)]
5+
pub enum Foo {
6+
Bar,
7+
}
8+
9+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: invalid syntax. expected #[export(key = "value")]
2+
--> tests/ui/export_fail_05.rs:4:10
3+
|
4+
4 | #[export(kind)]
5+
| ^^^^

gdnative/tests/ui/export_fail_06.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use gdnative::prelude::*;
2+
3+
#[derive(Export, ToVariant)]
4+
#[export(kinb = "enum")]
5+
pub enum Foo {
6+
Bar,
7+
}
8+
9+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: found kinb, expected kind
2+
--> tests/ui/export_fail_06.rs:4:10
3+
|
4+
4 | #[export(kinb = "enum")]
5+
| ^^^^

gdnative/tests/ui/export_fail_07.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use gdnative::prelude::*;
2+
3+
#[derive(Export, ToVariant)]
4+
#[export(kind = 123)]
5+
pub enum Foo {
6+
Bar,
7+
}
8+
9+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: string literal expected, wrap with double quotes
2+
--> tests/ui/export_fail_07.rs:4:17
3+
|
4+
4 | #[export(kind = 123)]
5+
| ^^^

gdnative/tests/ui/export_fail_08.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use gdnative::prelude::*;
2+
3+
#[derive(Export, ToVariant)]
4+
#[export(kind = "foo")]
5+
pub enum Foo {
6+
Bar,
7+
}
8+
9+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: unknown kind, expected enum
2+
--> tests/ui/export_fail_08.rs:4:17
3+
|
4+
4 | #[export(kind = "foo")]
5+
| ^^^^^

gdnative/tests/ui/export_fail_09.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use gdnative::prelude::*;
2+
3+
#[derive(Export, ToVariant)]
4+
#[export(kind = "enum")]
5+
#[export(kind = "enum")]
6+
pub enum Foo {
7+
Bar,
8+
}
9+
10+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: kind already set
2+
--> tests/ui/export_fail_09.rs:5:17
3+
|
4+
5 | #[export(kind = "enum")]
5+
| ^^^^^^

gdnative/tests/ui/export_pass.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use gdnative::prelude::*;
22

33
#[derive(Export, ToVariant, Clone, Copy)]
44
#[variant(enum = "repr")]
5+
#[export(kind = "enum")]
56
#[repr(i32)]
67
pub enum Foo {
78
Bar,

0 commit comments

Comments
 (0)