Skip to content
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

allow deriving of FromVariant for uninhabitable enums #962

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 48 additions & 46 deletions gdnative-derive/src/variant/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ pub(crate) fn expand_from_variant(derive_data: DeriveData) -> Result<TokenStream
}
}
Repr::Enum(variants) => {
if variants.is_empty() {
return Err(syn::Error::new(
ident.span(),
"cannot derive FromVariant for an uninhabited enum",
));
}

let var_input_ident = Ident::new("__enum_variant", Span::call_site());

let var_ident_strings: Vec<String> = variants
Expand All @@ -61,46 +54,55 @@ pub(crate) fn expand_from_variant(derive_data: DeriveData) -> Result<TokenStream

let var_input_ident_iter = std::iter::repeat(&var_input_ident);

// Return `FromVariantError` if input is an uninhabitable enum
let early_return = variants.is_empty().then(|| {
quote! {
return Err(FVE::UnknownEnumVariant {
variant: __key,
expected: &[],
});
}
});

quote! {
{
let __dict = ::gdnative::core_types::Dictionary::from_variant(#input_ident)
.map_err(|__err| FVE::InvalidEnumRepr {
expected: VariantEnumRepr::ExternallyTagged,
error: std::boxed::Box::new(__err),
})?;

let __keys = __dict.keys();
if __keys.len() != 1 {
Err(FVE::InvalidEnumRepr {
expected: VariantEnumRepr::ExternallyTagged,
error: std::boxed::Box::new(FVE::InvalidLength {
expected: 1,
len: __keys.len() as usize,
}),
})
}
else {
let __key = String::from_variant(&__keys.get(0))
.map_err(|__err| FVE::InvalidEnumRepr {
expected: VariantEnumRepr::ExternallyTagged,
error: std::boxed::Box::new(__err),
})?;
match __key.as_str() {
#(
#ref_var_ident_string_literals => {
let #var_input_ident_iter = &__dict.get_or_nil(&__keys.get(0));
(#var_from_variants).map_err(|err| FVE::InvalidEnumVariant {
variant: #ref_var_ident_string_literals,
error: std::boxed::Box::new(err),
})
},
)*
variant => Err(FVE::UnknownEnumVariant {
variant: variant.to_string(),
expected: &[#(#ref_var_ident_string_literals),*],
}),
}
}
Comment on lines -65 to -103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This {} block is no longer needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's almost identical to L68 ~ L105. (expected the early_return statement)

There are so many lines of change due to indentation. The outermost {} pair is removed because return_expr is the only expression used inside generated from_variant implementation. Should I keep it?

https://github.com/godot-rust/godot-rust/blob/94d7a45672825c916bfd5425b1703a0db1a822a5/gdnative-derive/src/variant/from.rs#L68-L105

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks. No it's fine, you can leave your changes like this 🙂

let __dict = ::gdnative::core_types::Dictionary::from_variant(#input_ident)
.map_err(|__err| FVE::InvalidEnumRepr {
expected: VariantEnumRepr::ExternallyTagged,
error: std::boxed::Box::new(__err),
})?;
let __keys = __dict.keys();
if __keys.len() != 1 {
return Err(FVE::InvalidEnumRepr {
expected: VariantEnumRepr::ExternallyTagged,
error: std::boxed::Box::new(FVE::InvalidLength {
expected: 1,
len: __keys.len() as usize,
}),
})
}

let __key = String::from_variant(&__keys.get(0))
.map_err(|__err| FVE::InvalidEnumRepr {
expected: VariantEnumRepr::ExternallyTagged,
error: std::boxed::Box::new(__err),
})?;

#early_return

match __key.as_str() {
#(
#ref_var_ident_string_literals => {
let #var_input_ident_iter = &__dict.get_or_nil(&__keys.get(0));
(#var_from_variants).map_err(|err| FVE::InvalidEnumVariant {
variant: #ref_var_ident_string_literals,
error: std::boxed::Box::new(err),
})
},
)*
variant => Err(FVE::UnknownEnumVariant {
variant: variant.to_string(),
expected: &[#(#ref_var_ident_string_literals),*],
}),
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions gdnative-derive/src/variant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,17 @@ pub(crate) fn derive_from_variant(derive_input: DeriveInput) -> Result<TokenStre
let variant = parse_derive_input(derive_input, &bound, Direction::From);
from::expand_from_variant(variant?)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn derive_from_variant_on_uninhabitable_enums() {
let input = parse_quote! {
enum Test {}
};

derive_from_variant(input).unwrap();
}
}
14 changes: 14 additions & 0 deletions test/src/test_derive.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::cell::{self, Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;

use gdnative::export::Property;
Expand Down Expand Up @@ -144,6 +145,19 @@ crate::godot_itest! { test_derive_to_variant {
Ok(ToVarTuple::<f64, i128>(1, 0, false)),
ToVarTuple::from_variant(&variant)
);

// Derive on uninhabitable enum results an error
#[derive(Debug, PartialEq, FromVariant)]
enum NoVariant {}

let input = HashMap::from_iter([("foo", "bar")]).to_variant();
assert_eq!(
NoVariant::from_variant(&input),
Err(FromVariantError::UnknownEnumVariant {
variant: "foo".into(),
expected: &[]
})
);
}}

// ----------------------------------------------------------------------------------------------------------------------------------------------
Expand Down