-
Notifications
You must be signed in to change notification settings - Fork 212
Enum property with custom int discriminants #775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Bogay
wants to merge
19
commits into
godot-rust:master
Choose a base branch
from
Bogay:custom-enum-int
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
0f558c6
feat(EnumHint): store optional mapping value information
Bogay 89cbefc
feat(derive): add `ExportEnum`
Bogay d464e63
test(EnumHint): test g`to_godot_hint_string`
Bogay 29458ae
fix(ExportEnum): handle non-enum input
Bogay 2754088
test(ExportEnum): correctly deny invalid input
Bogay 512dc08
feat(ExportEnum): correctly return error for `from_variant`
Bogay 5b2e639
test(ExportEnum): to/from variant
Bogay aeac5af
docs(ExportEnum): add macro doc
Bogay cb9087a
feat: rename ExportEnum to Export
Bogay 217d253
test(Export): add UI test
Bogay 5936bed
test(Export): derive ToVariant in doctest
Bogay 7d7bdd7
test: derive repr for enum
Bogay a12fd4d
feat(Export): report compile error for all non-fieldless enum variants
Bogay c33e89b
test(Export): add UI tests
Bogay 825ad68
docs(Export): fix example code
Bogay 98e0f4f
feat(EnumHint): add EnumHintEntry to store entry info
Bogay e055ea2
test: remove duplicated tests
Bogay 7159dd9
feat(Export): add #[export(...)] attribute
Bogay 348bac2
test(ui): use glob to match tests for #[derive(Export)]
Bogay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
use crate::crate_gdnative_core; | ||
use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; | ||
use quote::ToTokens; | ||
use syn::spanned::Spanned; | ||
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<DeriveData> { | ||
let DeriveInput { | ||
ident, data, attrs, .. | ||
} = input.clone(); | ||
|
||
let (kind, errors) = attrs | ||
.iter() | ||
.filter(|attr| attr.path.is_ident("export")) | ||
.fold((None, vec![]), |(mut kind, mut errors), attr| { | ||
let list = match attr.parse_meta() { | ||
Ok(meta) => match meta { | ||
Meta::List(list) => list, | ||
Meta::Path(path) => { | ||
errors.push(syn::Error::new( | ||
path.span(), | ||
"missing macro arguments. expected #[export(...)]", | ||
)); | ||
return (kind, errors); | ||
} | ||
Meta::NameValue(pair) => { | ||
errors.push(syn::Error::new( | ||
pair.span(), | ||
"missing macro arguments. expected #[export(...)]", | ||
)); | ||
return (kind, errors); | ||
} | ||
}, | ||
Err(e) => { | ||
errors.push(syn::Error::new( | ||
e.span(), | ||
format!("unknown attribute format. expected #[export(...)]: {e}"), | ||
)); | ||
return (kind, errors); | ||
} | ||
}; | ||
|
||
for meta in list.nested.into_iter() { | ||
let syn::NestedMeta::Meta(Meta::NameValue(pair)) = meta else { | ||
errors.push(syn::Error::new( | ||
meta.span(), | ||
"invalid syntax. expected #[export(key = \"value\")]", | ||
)); | ||
continue; | ||
}; | ||
|
||
if !pair.path.is_ident("kind") { | ||
errors.push(syn::Error::new( | ||
pair.span(), | ||
format!("found {}, expected kind", pair.path.into_token_stream()), | ||
)); | ||
continue; | ||
} | ||
|
||
let syn::Lit::Str(str) = pair.lit else { | ||
errors.push(syn::Error::new( | ||
pair.lit.span(), | ||
"string literal expected, wrap with double quotes", | ||
)); | ||
continue; | ||
}; | ||
|
||
match str.value().as_str() { | ||
"enum" => { | ||
if kind.is_some() { | ||
errors.push(syn::Error::new(str.span(), "kind already set")); | ||
} else { | ||
kind = Some(Kind::Enum); | ||
} | ||
} | ||
_ => { | ||
errors.push(syn::Error::new(str.span(), "unknown kind, expected enum")); | ||
} | ||
} | ||
} | ||
|
||
(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<TokenStream2> { | ||
let derive_data = parse_derive_input(input)?; | ||
|
||
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<TokenStream2> { | ||
let err = data | ||
.variants | ||
.iter() | ||
.filter(|variant| !matches!(variant.fields, Fields::Unit)) | ||
.map(|variant| err_only_supports_fieldless_enums(variant.ident.span())) | ||
.reduce(|mut acc, err| { | ||
acc.combine(err); | ||
acc | ||
}); | ||
if let Some(err) = err { | ||
return Err(err); | ||
} | ||
|
||
let gdnative_core = crate_gdnative_core(); | ||
let mappings = data | ||
.variants | ||
.iter() | ||
.map(|variant| { | ||
let key = &variant.ident; | ||
let val = quote! { #enum_ty::#key as i64 }; | ||
quote! { #gdnative_core::export::hint::EnumHintEntry::with_value(stringify!(#key).to_string(), #val) } | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let impl_block = quote! { | ||
const _: () = { | ||
pub enum NoHint {} | ||
|
||
impl #gdnative_core::export::Export for #enum_ty { | ||
type Hint = NoHint; | ||
|
||
#[inline] | ||
fn export_info(_hint: Option<Self::Hint>) -> #gdnative_core::export::ExportInfo { | ||
let mappings = vec![ #(#mappings),* ]; | ||
let enum_hint = #gdnative_core::export::hint::EnumHint::with_entries(mappings); | ||
return #gdnative_core::export::hint::IntHint::<i64>::Enum(enum_hint).export_info(); | ||
} | ||
} | ||
}; | ||
}; | ||
|
||
Ok(impl_block) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
use gdnative::prelude::*; | ||
|
||
#[derive(Export, ToVariant)] | ||
#[export(kind = "enum")] | ||
pub enum Foo { | ||
Bar(String), | ||
Baz { a: i32, b: u32 }, | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
error: #[derive(Export)] only supports fieldless enums | ||
--> tests/ui/export_fail_01.rs:6:5 | ||
| | ||
6 | Bar(String), | ||
| ^^^ | ||
|
||
error: #[derive(Export)] only supports fieldless enums | ||
--> tests/ui/export_fail_01.rs:7:5 | ||
| | ||
7 | Baz { a: i32, b: u32 }, | ||
| ^^^ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
use gdnative::prelude::*; | ||
|
||
#[derive(Export, ToVariant)] | ||
#[export(kind = "enum")] | ||
pub struct Foo { | ||
bar: i32, | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
error: #[derive(Export)] only supports fieldless enums | ||
--> tests/ui/export_fail_02.rs:5:5 | ||
| | ||
5 | pub struct Foo { | ||
| ^^^^^^ |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.