-
-
Notifications
You must be signed in to change notification settings - Fork 210
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
Rewrite the derive macros related to GodotConvert
#599
Rewrite the derive macros related to GodotConvert
#599
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-599 |
5a90282
to
c7838f5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, very rigorous! 👍
Added some comments, mostly about some naming consistency aspects.
if parser.handle_alone("transparent")? { | ||
parser.finish()?; | ||
|
||
return Ok(Self::Transparent { span }); | ||
} | ||
|
||
let via_type = parser.handle_ident_required("via")?; | ||
|
||
let via_type = ViaType::parse_ident(via_type)?; | ||
|
||
parser.finish()?; | ||
|
||
Ok(Self::Via { via_type }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if parser.handle_alone("transparent")? { | |
parser.finish()?; | |
return Ok(Self::Transparent { span }); | |
} | |
let via_type = parser.handle_ident_required("via")?; | |
let via_type = ViaType::parse_ident(via_type)?; | |
parser.finish()?; | |
Ok(Self::Via { via_type }) | |
let attr = if parser.handle_alone("transparent")? { | |
Self::Transparent { span } | |
} else { | |
let via_type = parser.handle_ident_required("via")?; | |
let via_type = ViaType::parse_ident(via_type)?; | |
Self::Via { via_type } | |
}; | |
parser.finish()?; | |
Ok(via) |
would only need a single finish()
call.
But we may anyway need to rewrite this a bit in the future, as it will be possible to specify multiple keys, not just one. So up to you.
Please avoid an empty line after every single statement though 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i rewrote it a bit by adding an extra function that takes the parser so i can just do
pub fn parse_attribute(declaration: &Declaration) -> ParseResult<Self> {
let mut parser = KvParser::parse_required(declaration.attributes(), "godot", declaration)?;
let attribute = Self::parse(&mut parser)?;
parser.finish()?;
Ok(attribute)
}
also i made via
use handle_ident
instead cause i just realized handle_ident_required
may provide suboptimal error messages. as in it'd complain that via
is required, when really either transparent
or via
is required.
fn create_discriminant_mapping( | ||
variants: Vec<UnitEnumVariant>, | ||
) -> ParseResult<(Vec<Ident>, Vec<Literal>)> { | ||
// See here for how implicit discriminants are decided | ||
// https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants | ||
let mut names = Vec::new(); | ||
let mut discriminants = Vec::new(); | ||
|
||
let mut last_discriminant = None; | ||
for variant in variants.into_iter() { | ||
let discriminant_span = variant.discriminant_span(); | ||
|
||
let discriminant = match variant.discriminant_as_i64()? { | ||
Some(discriminant) => discriminant, | ||
None => last_discriminant.unwrap_or(0) + 1, | ||
}; | ||
last_discriminant = Some(discriminant); | ||
|
||
let mut discriminant = Literal::i64_unsuffixed(discriminant); | ||
discriminant.set_span(discriminant_span); | ||
|
||
names.push(variant.name); | ||
discriminants.push(discriminant) | ||
} | ||
|
||
Ok((names, discriminants)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably OK to talk about "discriminants" here, as they refer to the syntactic part at this stage. For example, they can be implicit (SomeName
) or explicit (SomeName = 12
).
Calling them "ords" afterwards would then help to separate this syntactic from the semantic concept -- tokens vs. numbers.
Like, this is very cool , it will be much nicer to work with c like enums from godot but we do lose the possibility to derive to/from godot for structs with more than one field and enum with variants containing data |
All good for me, thanks! I think the defaults we can discuss in detail when it comes to the follow-up PRs 👍 As for |
Yes, we are currently in step 1 according to this comment. |
c7838f5
to
28cd3c1
Compare
} | ||
|
||
fn create_discriminant_mapping( | ||
variants: Vec<CStyleEnumVariant>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably also here:
variants: Vec<CStyleEnumVariant>, | |
enumerators: Vec<CStyleEnumerator>, |
Especially since in our case Variant
has a prominent different meaning, this helps keep terminology apart.
/// Derives `ToGodot` for newtype structs. | ||
fn to_newtype(name: &Ident, field: &NewtypeStruct) -> ParseResult<TokenStream> { | ||
let field_name = field.field_name(); | ||
let via_type = &field.ty; | ||
|
||
Ok(quote! { | ||
impl ::godot::builtin::meta::ToGodot for #name { | ||
fn to_godot(&self) -> #via_type { | ||
::godot::builtin::meta::ToGodot::to_godot(&self.#field_name) | ||
} | ||
|
||
fn into_godot(self) -> #via_type { | ||
::godot::builtin::meta::ToGodot::into_godot(self.#field_name) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// Derives `ToGodot` for enums with a via type of integers. | ||
fn to_enum_int(name: &Ident, enum_: &CStyleEnum, int: &Ident) -> ParseResult<TokenStream> { | ||
let discriminants = enum_.discriminants(); | ||
let names = enum_.names(); | ||
|
||
Ok(quote! { | ||
impl ::godot::builtin::meta::ToGodot for #name { | ||
fn to_godot(&self) -> #int { | ||
match self { | ||
#( | ||
#name::#names => #discriminants, | ||
)* | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// Derives `ToGodot` for enums with a via type of `GString`. | ||
fn to_enum_string(name: &Ident, enum_: &CStyleEnum) -> ParseResult<TokenStream> { | ||
let names = enum_.names(); | ||
let names_str = names.iter().map(ToString::to_string).collect::<Vec<_>>(); | ||
|
||
Ok(quote! { | ||
impl ::godot::builtin::meta::ToGodot for #name { | ||
fn to_godot(&self) -> ::godot::builtin::GString { | ||
match self { | ||
#( | ||
#name::#names => #names_str.into(), | ||
)* | ||
} | ||
} | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These methods never return Err
.
Also, typically the methods that generate token streams start with make_
. I would probably also include the generated item in the name; it doesn't hurt if they become a bit longer. For example:
make_togodot_for_newtype_struct
make_togodot_for_int_enum
make_togodot_for_gstring_enum
@@ -147,18 +147,18 @@ impl INode for HasProperty { | |||
|
|||
#[derive(Default, Copy, Clone)] | |||
#[repr(i64)] | |||
enum SomeCStyleEnum { | |||
enum SomeUnitEnum { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This renaming can probably be undone now 🙂
/// This expects a derived [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) implementation, using a manual | ||
/// implementation of `GodotConvert` may lead to incorrect values being displayed in Godot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you encountered this? I think it's good advice, but I wonder what concretely you thought of here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you did say
#[derive(Var, Export)]
#[godot(via = i64)]
pub enum MyEnum {
A,
B,
}
impl GodotConvert for MyEnum {
type Via = i64;
}
impl FromGodot for MyEnum {
fn try_from_Godot(via: Self::Via) -> Result<..> {
match via {
1 => Ok(Self::A),
0 => Ok(Self::B),
_ => Err(..)
}
}
}
// And corresponding ToGodot
Then the Var
and Export
impl would map A
and B
to each other, since the assumption is that A = 0
and B = 1
since that's their rust discriminants.
We could probably fix this by making the property hint impls instead do something like
let mut s = Vec::new();
s.push(format!("{}:{}", "A", Self::A.to_godot());
// repeat for each variant
s.join(",").into()
Though this would add a runtime cost to using it in the editor, but also that's maybe not a big deal? rust might be able to optimize it fairly well anyway.
This could still lead to inconsistencies if FromGodot
and ToGodot
arent inverses. But that's just a general issue with implementing those two traits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for elaborating!
Your suggestion sounds good, but would require that any ToGodot
implementation by the user here also support Display
-- and that Display
then match the property-hint format. It's mixing responsibilities a bit. Either a dedicated method/trait, or conversion to Variant
and using its Display
might be an option...
But for now, the limitation is OK I think, especially since it's documented.
…t` derive macros
Document things properly Other cleanup
28cd3c1
to
4b445c6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot, looks much cleaner now!
/// This expects a derived [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) implementation, using a manual | ||
/// implementation of `GodotConvert` may lead to incorrect values being displayed in Godot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for elaborating!
Your suggestion sounds good, but would require that any ToGodot
implementation by the user here also support Display
-- and that Display
then match the property-hint format. It's mixing responsibilities a bit. Either a dedicated method/trait, or conversion to Variant
and using its Display
might be an option...
But for now, the limitation is OK I think, especially since it's documented.
Rewrites the
GodotConvert
+ related macros.Removes
ToGodot
andFromGodot
derive macros, they are now also derived byGodotConvert
.Largely follows what's laid out in this comment #452 (comment), however for implicit discriminants i follow rust's algorithm for calculating them instead as otherwise you can get some unexpected inconsistencies.
As for the questions from this comment :
This PR currently does the following:
transparent
for structs andGString
for enums in that case.StringName
naively. However if we wantStringName
to be interned or do anything differently thanGString
that would be a bit more complicated.GodotConvert
also derivesToGodot
andFromGodot
, but there is no opt-out currently.closes #452