-
-
Notifications
You must be signed in to change notification settings - Fork 297
Add PhantomData<T> support for the GodotConvert derive macro #1619
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
base: master
Are you sure you want to change the base?
Changes from all commits
bb74ca4
3703c12
557ae08
1d6debe
5ef1953
60d1a3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| use proc_macro2::{Ident, TokenStream}; | ||
| use quote::ToTokens; | ||
| use venial::{GenericParamList, WhereClause}; | ||
|
|
||
| use super::c_style_enum::CStyleEnum; | ||
| use super::godot_attribute::{GodotAttribute, ViaType}; | ||
|
|
@@ -18,23 +19,41 @@ use crate::util::bail; | |
| pub struct GodotConvert { | ||
| /// The name of the type we're deriving for. | ||
| pub ty_name: Ident, | ||
| pub where_clause: Option<WhereClause>, | ||
| pub generic_params: Option<GenericParamList>, | ||
| /// The data from the type and `godot` attribute. | ||
| pub convert_type: ConvertType, | ||
| } | ||
|
|
||
| impl GodotConvert { | ||
| pub fn parse_declaration(item: venial::Item) -> ParseResult<Self> { | ||
| let (name, where_clause, generic_params) = match &item { | ||
| venial::Item::Struct(struct_) => ( | ||
| struct_.name.clone(), | ||
| &struct_.where_clause, | ||
| &struct_.generic_params, | ||
| ), | ||
| venial::Item::Enum(enum_) => ( | ||
| enum_.name.clone(), | ||
| &enum_.where_clause, | ||
| &enum_.generic_params, | ||
| ), | ||
| let data = ConvertType::parse_declaration(&item)?; | ||
|
|
||
| let (name, where_clause, generic_params) = match item { | ||
| venial::Item::Struct(struct_) => { | ||
| (struct_.name, struct_.where_clause, struct_.generic_params) | ||
| } | ||
| venial::Item::Enum(enum_) => { | ||
| // We only have C-style enums, so Rust would already complain that generics are unused. | ||
| // This provides clearer error messages though. | ||
| if let Some(generic_params) = &enum_.generic_params { | ||
| return bail!( | ||
| generic_params, | ||
| "#[derive(GodotConvert)] does not support lifetimes or generic parameters on enums" | ||
| ); | ||
| } | ||
|
|
||
| // Is this check even necessary? What's the use case of where clauses without generics? | ||
| // For traits, one can imagine `Self: SomeBound`, but for structs/enums? | ||
| if let Some(where_clause) = &enum_.where_clause { | ||
| return bail!( | ||
| where_clause, | ||
| "#[derive(GodotConvert)] does not support where clauses" | ||
| ); | ||
| } | ||
|
|
||
| (enum_.name, enum_.where_clause, enum_.generic_params) | ||
| } | ||
| other => { | ||
| return bail!( | ||
| other, | ||
|
|
@@ -43,26 +62,10 @@ impl GodotConvert { | |
| } | ||
| }; | ||
|
|
||
| if let Some(generic_params) = generic_params { | ||
| return bail!( | ||
| generic_params, | ||
| "#[derive(GodotConvert)] does not support lifetimes or generic parameters" | ||
| ); | ||
| } | ||
|
|
||
| // Is this check even necessary? What's the use case of where clauses without generics? | ||
| // For traits, one can imagine `Self: SomeBound`, but for structs/enums? | ||
| if let Some(where_clause) = where_clause { | ||
| return bail!( | ||
| where_clause, | ||
| "#[derive(GodotConvert)] does not support where clauses" | ||
| ); | ||
| } | ||
|
|
||
| let data = ConvertType::parse_declaration(item)?; | ||
|
|
||
| Ok(Self { | ||
| ty_name: name, | ||
| where_clause: where_clause, | ||
| generic_params: generic_params, | ||
| convert_type: data, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here, if
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| }) | ||
| } | ||
|
|
@@ -77,10 +80,10 @@ pub enum ConvertType { | |
| } | ||
|
|
||
| impl ConvertType { | ||
| pub fn parse_declaration(item: venial::Item) -> ParseResult<Self> { | ||
| let attribute = GodotAttribute::parse_attribute(&item)?; | ||
| pub fn parse_declaration(item: &venial::Item) -> ParseResult<Self> { | ||
| let attribute = GodotAttribute::parse_attribute(item)?; | ||
|
|
||
| match &item { | ||
| match item { | ||
| venial::Item::Struct(struct_) => { | ||
| let GodotAttribute::Transparent { .. } = attribute else { | ||
| return bail!( | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,18 +5,34 @@ | |||||||||||||||
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||||||||||||||
| */ | ||||||||||||||||
|
|
||||||||||||||||
| use proc_macro2::{Ident, TokenStream}; | ||||||||||||||||
| use proc_macro2::{Ident, Literal, Span, TokenStream}; | ||||||||||||||||
| use quote::quote; | ||||||||||||||||
| use venial::{NamedField, TupleField}; | ||||||||||||||||
|
|
||||||||||||||||
| use crate::ParseResult; | ||||||||||||||||
| use crate::util::bail; | ||||||||||||||||
|
|
||||||||||||||||
| pub enum FieldType { | ||||||||||||||||
| Tuple(Literal), | ||||||||||||||||
| Named(Ident), | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| pub enum FieldsType { | ||||||||||||||||
| Tuple(Vec<Literal>), | ||||||||||||||||
| Named(Vec<Ident>), | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// Stores info from the field of a newtype struct for use in deriving `GodotConvert` and other related traits. | ||||||||||||||||
| /// | ||||||||||||||||
| /// Here, a newtype struct must have exactly 1 non-ZST field, and can have an arbitrary amount of ZST fields. | ||||||||||||||||
| pub struct NewtypeStruct { | ||||||||||||||||
| /// The name of the field. | ||||||||||||||||
| /// | ||||||||||||||||
| /// If `None`, then this represents a tuple-struct with one field. | ||||||||||||||||
| pub name: Option<Ident>, | ||||||||||||||||
| pub name: FieldType, | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "name" vs. "field type". One of the two is badly named. Also, unrelated to your changes -- I noticed that we call the type |
||||||||||||||||
|
|
||||||||||||||||
| /// The names of the phantom fields. | ||||||||||||||||
| pub phantom_names: FieldsType, | ||||||||||||||||
|
|
||||||||||||||||
| /// The type of the field. | ||||||||||||||||
| pub ty: venial::TypeExpr, | ||||||||||||||||
|
|
@@ -33,34 +49,99 @@ impl NewtypeStruct { | |||||||||||||||
| "GodotConvert expects a struct with a single field, unit structs are currently not supported" | ||||||||||||||||
| ), | ||||||||||||||||
| venial::Fields::Tuple(fields) => { | ||||||||||||||||
| if fields.fields.len() != 1 { | ||||||||||||||||
| fn phantom_predicate(field: &TupleField) -> bool { | ||||||||||||||||
| // Some types we don't care about are not paths, like references | ||||||||||||||||
| if let Some(path) = field.ty.as_path() { | ||||||||||||||||
| // This unwrap only fails if the field had no type specified, which isn't valid code anyways. | ||||||||||||||||
| return path.segments.last().unwrap().ident | ||||||||||||||||
| == Ident::new("PhantomData", Span::mixed_site()); | ||||||||||||||||
| } | ||||||||||||||||
| false | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+52
to
+60
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Also, can you use this maybe? gdext/godot-macros/src/util/mod.rs Lines 247 to 253 in 1a9e41f
And maybe more generally, checking on the syntax level against types is brittle, and we can't easily cover edge cases. Do we have to do it? |
||||||||||||||||
|
|
||||||||||||||||
| let mut non_phantom_fields = fields | ||||||||||||||||
| .fields | ||||||||||||||||
| .items() | ||||||||||||||||
| .enumerate() | ||||||||||||||||
| .filter(|(_, field)| !phantom_predicate(field)); | ||||||||||||||||
|
|
||||||||||||||||
| let maybe_field = non_phantom_fields.next(); | ||||||||||||||||
|
|
||||||||||||||||
| let total_count = if maybe_field.is_none() { | ||||||||||||||||
| 0 | ||||||||||||||||
| } else { | ||||||||||||||||
| non_phantom_fields.count() + 1 | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| if total_count != 1 { | ||||||||||||||||
| return bail!( | ||||||||||||||||
| &fields.fields, | ||||||||||||||||
| "GodotConvert expects a struct with a single field, not {} fields", | ||||||||||||||||
| fields.fields.len() | ||||||||||||||||
| "GodotConvert expects a struct with a single non-PhantomData field, not {} fields", | ||||||||||||||||
| total_count | ||||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| let (field, _) = fields.fields[0].clone(); | ||||||||||||||||
| let (field_num, field) = maybe_field.unwrap(); | ||||||||||||||||
|
|
||||||||||||||||
| let phantom_nums = (0..field_num) | ||||||||||||||||
| .chain(field_num + 1..fields.fields.len()) | ||||||||||||||||
| .map(Literal::usize_unsuffixed) | ||||||||||||||||
| .collect(); | ||||||||||||||||
|
|
||||||||||||||||
| Ok(NewtypeStruct { | ||||||||||||||||
| name: None, | ||||||||||||||||
| ty: field.ty, | ||||||||||||||||
| name: FieldType::Tuple(Literal::usize_unsuffixed(field_num)), | ||||||||||||||||
| phantom_names: FieldsType::Tuple(phantom_nums), | ||||||||||||||||
| ty: field.ty.clone(), | ||||||||||||||||
| }) | ||||||||||||||||
| } | ||||||||||||||||
| venial::Fields::Named(fields) => { | ||||||||||||||||
| if fields.fields.len() != 1 { | ||||||||||||||||
| fn phantom_predicate(field: &NamedField) -> bool { | ||||||||||||||||
| // Some types we don't care about are not paths, like references | ||||||||||||||||
| if let Some(path) = field.ty.as_path() { | ||||||||||||||||
| // This unwrap only fails if the field had no type specified, which isn't valid code anyways. | ||||||||||||||||
| return path.segments.last().unwrap().ident | ||||||||||||||||
| == Ident::new("PhantomData", Span::mixed_site()); | ||||||||||||||||
| } | ||||||||||||||||
| false | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| let mut non_phantom_fields = fields | ||||||||||||||||
| .fields | ||||||||||||||||
| .items() | ||||||||||||||||
| .filter(|field| !phantom_predicate(field)); | ||||||||||||||||
|
|
||||||||||||||||
| let maybe_field = non_phantom_fields.next(); | ||||||||||||||||
|
|
||||||||||||||||
| let total_count = if maybe_field.is_none() { | ||||||||||||||||
| 0 | ||||||||||||||||
| } else { | ||||||||||||||||
| non_phantom_fields.count() + 1 | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| if total_count != 1 { | ||||||||||||||||
| return bail!( | ||||||||||||||||
| &fields.fields, | ||||||||||||||||
| "GodotConvert expects a struct with a single field, not {} fields", | ||||||||||||||||
| fields.fields.len() | ||||||||||||||||
| "GodotConvert expects a struct with a single non-PhantomData field, not {} fields", | ||||||||||||||||
| total_count | ||||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+98
to
127
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code duplication... |
||||||||||||||||
|
|
||||||||||||||||
| let (field, _) = fields.fields[0].clone(); | ||||||||||||||||
| let field = maybe_field.unwrap().clone(); | ||||||||||||||||
|
|
||||||||||||||||
| let phantom_names = fields | ||||||||||||||||
| .fields | ||||||||||||||||
| .items() | ||||||||||||||||
| .filter_map(|field| { | ||||||||||||||||
| if phantom_predicate(field) { | ||||||||||||||||
| return Some(field.name.clone()); | ||||||||||||||||
| } | ||||||||||||||||
| None | ||||||||||||||||
| }) | ||||||||||||||||
| .collect(); | ||||||||||||||||
|
|
||||||||||||||||
| Ok(NewtypeStruct { | ||||||||||||||||
| name: Some(field.name), | ||||||||||||||||
| name: FieldType::Named(field.name), | ||||||||||||||||
| phantom_names: FieldsType::Named(phantom_names), | ||||||||||||||||
| ty: field.ty, | ||||||||||||||||
| }) | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -69,7 +150,7 @@ impl NewtypeStruct { | |||||||||||||||
|
|
||||||||||||||||
| /// Gets the field name. | ||||||||||||||||
| /// | ||||||||||||||||
| /// If this represents a tuple-struct, then it will return `0`. This can be used just like it was a named field with the name `0`. | ||||||||||||||||
| /// If this represents a tuple-struct, then it will return a number. This can be used just like it was a named field. | ||||||||||||||||
| /// For instance: | ||||||||||||||||
| /// ``` | ||||||||||||||||
| /// struct Foo(i64); | ||||||||||||||||
|
|
@@ -80,8 +161,18 @@ impl NewtypeStruct { | |||||||||||||||
| /// ``` | ||||||||||||||||
| pub fn field_name(&self) -> TokenStream { | ||||||||||||||||
| match &self.name { | ||||||||||||||||
| Some(name) => quote! { #name }, | ||||||||||||||||
| None => quote! { 0 }, | ||||||||||||||||
| FieldType::Named(name) => quote! { #name }, | ||||||||||||||||
| FieldType::Tuple(num) => quote! { #num }, | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// Gets the phantom field names. | ||||||||||||||||
| /// | ||||||||||||||||
| /// If this represents a tuple-struct, then it will return numbers. See `Self::field_name` | ||||||||||||||||
| pub fn phantom_field_names(&self) -> Vec<TokenStream> { | ||||||||||||||||
| match &self.phantom_names { | ||||||||||||||||
| FieldsType::Named(vec) => vec.iter().map(|ident| quote! {#ident}).collect(), | ||||||||||||||||
| FieldsType::Tuple(vec) => vec.iter().map(|ident| quote! {#ident}).collect(), | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
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.
Shorter, no empty line: