-
-
Notifications
You must be signed in to change notification settings - Fork 161
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #599 from lilizoey/refactor/rewrite-convert-derives
Rewrite the derive macros related to `GodotConvert`
- Loading branch information
Showing
20 changed files
with
888 additions
and
1,180 deletions.
There are no files selected for viewing
This file contains 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,150 @@ | ||
/* | ||
* Copyright (c) godot-rust; Bromeon and contributors. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
use proc_macro2::{Ident, Literal, Span, TokenTree}; | ||
|
||
use crate::util::{bail, error}; | ||
use crate::ParseResult; | ||
|
||
/// Stores info from c-style enums for use in deriving `GodotConvert` and other related traits. | ||
#[derive(Clone, Debug)] | ||
pub struct CStyleEnum { | ||
/// The names of each variant. | ||
enumerator_names: Vec<Ident>, | ||
/// The discriminants of each variant, both explicit and implicit. | ||
enumerator_ords: Vec<Literal>, | ||
} | ||
|
||
impl CStyleEnum { | ||
/// Parses the enum. | ||
/// | ||
/// Ensures all the variants are unit variants, and that any explicit discriminants are integer literals. | ||
pub fn parse_enum(enum_: &venial::Enum) -> ParseResult<Self> { | ||
let variants = enum_ | ||
.variants | ||
.items() | ||
.map(CStyleEnumerator::parse_enum_variant) | ||
.collect::<ParseResult<Vec<_>>>()?; | ||
|
||
let (names, discriminants) = Self::create_discriminant_mapping(variants)?; | ||
|
||
Ok(Self { | ||
enumerator_names: names, | ||
enumerator_ords: discriminants, | ||
}) | ||
} | ||
|
||
fn create_discriminant_mapping( | ||
enumerators: Vec<CStyleEnumerator>, | ||
) -> 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 enumerator in enumerators.into_iter() { | ||
let discriminant_span = enumerator.discriminant_span(); | ||
|
||
let discriminant = match enumerator.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(enumerator.name); | ||
discriminants.push(discriminant) | ||
} | ||
|
||
Ok((names, discriminants)) | ||
} | ||
|
||
/// Returns the names of the variants, in order of the variants. | ||
pub fn names(&self) -> &[Ident] { | ||
&self.enumerator_names | ||
} | ||
|
||
/// Returns the discriminants of each variant, in order of the variants. | ||
pub fn discriminants(&self) -> &[Literal] { | ||
&self.enumerator_ords | ||
} | ||
|
||
/// Return a hint string for use with `PropertyHint::ENUM` where each variant has an explicit integer hint. | ||
pub fn to_int_hint(&self) -> String { | ||
self.enumerator_names | ||
.iter() | ||
.zip(self.enumerator_ords.iter()) | ||
.map(|(name, discrim)| format!("{name}:{discrim}")) | ||
.collect::<Vec<_>>() | ||
.join(",") | ||
} | ||
|
||
/// Return a hint string for use with `PropertyHint::ENUM` where the variants are just kept as strings. | ||
pub fn to_string_hint(&self) -> String { | ||
self.enumerator_names | ||
.iter() | ||
.map(ToString::to_string) | ||
.collect::<Vec<_>>() | ||
.join(",") | ||
} | ||
} | ||
|
||
/// Each variant in a c-style enum. | ||
#[derive(Clone, Debug)] | ||
pub struct CStyleEnumerator { | ||
/// The name of the variant. | ||
name: Ident, | ||
/// The explicit discriminant of the variant, `None` means there was no explicit discriminant. | ||
discriminant: Option<TokenTree>, | ||
} | ||
|
||
impl CStyleEnumerator { | ||
/// Parse an enum variant, erroring if it isn't a unit variant. | ||
fn parse_enum_variant(enum_variant: &venial::EnumVariant) -> ParseResult<Self> { | ||
match enum_variant.contents { | ||
venial::StructFields::Unit => {} | ||
_ => { | ||
return bail!( | ||
&enum_variant.contents, | ||
"GodotConvert only supports c-style enums" | ||
) | ||
} | ||
} | ||
|
||
Ok(Self { | ||
name: enum_variant.name.clone(), | ||
discriminant: enum_variant.value.as_ref().map(|val| &val.value).cloned(), | ||
}) | ||
} | ||
|
||
/// Returns the discriminant parsed as an i64 literal. | ||
fn discriminant_as_i64(&self) -> ParseResult<Option<i64>> { | ||
let Some(discriminant) = self.discriminant.as_ref() else { | ||
return Ok(None); | ||
}; | ||
|
||
let int = discriminant | ||
.to_string() | ||
.parse::<i64>() | ||
.map_err(|_| error!(discriminant, "expected i64 literal"))?; | ||
|
||
Ok(Some(int)) | ||
} | ||
|
||
/// Returns a span suitable for the discriminant of the variant. | ||
/// | ||
/// If there was no explicit discriminant, this will use the span of the name instead. | ||
fn discriminant_span(&self) -> Span { | ||
match &self.discriminant { | ||
Some(discriminant) => discriminant.span(), | ||
None => self.name.span(), | ||
} | ||
} | ||
} |
This file contains 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,90 @@ | ||
/* | ||
* Copyright (c) godot-rust; Bromeon and contributors. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
use proc_macro2::{Ident, Span, TokenStream}; | ||
use quote::ToTokens; | ||
use venial::Declaration; | ||
|
||
use crate::util::{bail, KvParser}; | ||
use crate::ParseResult; | ||
|
||
/// Stores data related to the `#[godot(..)]` attribute. | ||
pub enum GodotAttribute { | ||
/// `#[godot(transparent)]` | ||
Transparent { span: Span }, | ||
/// `#[godot(via = via_type)]` | ||
Via { span: Span, via_type: ViaType }, | ||
} | ||
|
||
impl GodotAttribute { | ||
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) | ||
} | ||
|
||
fn parse(parser: &mut KvParser) -> ParseResult<Self> { | ||
let span = parser.span(); | ||
|
||
if parser.handle_alone("transparent")? { | ||
return Ok(Self::Transparent { span }); | ||
} | ||
|
||
if let Some(via_type) = parser.handle_ident("via")? { | ||
return Ok(Self::Via { | ||
span, | ||
via_type: ViaType::parse_ident(via_type)?, | ||
}); | ||
} | ||
|
||
bail!( | ||
span, | ||
"expected either `#[godot(transparent)]` or `#[godot(via = <via_type>)]`" | ||
) | ||
} | ||
|
||
/// The span of the entire attribute. | ||
/// | ||
/// Specifically this is the span of the `[ ]` group from a `#[godot(...)]` attribute. | ||
pub fn span(&self) -> Span { | ||
match self { | ||
GodotAttribute::Transparent { span } => *span, | ||
GodotAttribute::Via { span, .. } => *span, | ||
} | ||
} | ||
} | ||
|
||
/// The via type from a `#[godot(via = via_type)]` attribute. | ||
pub enum ViaType { | ||
/// The via type is `GString` | ||
GString { gstring_ident: Ident }, | ||
/// The via type is an integer | ||
Int { int_ident: Ident }, | ||
} | ||
|
||
impl ViaType { | ||
fn parse_ident(ident: Ident) -> ParseResult<Self> { | ||
let via_type = match ident.to_string().as_str() { | ||
"GString" => ViaType::GString { gstring_ident: ident }, | ||
"i8" |"i16" | "i32" | "i64" | "u8" | "u16" | "u32" => ViaType::Int { int_ident: ident }, | ||
other => return bail!(ident, "Via type `{other}` is not supported, expected one of: GString, i8, i16, i32, i64, u8, u16, u32") | ||
}; | ||
|
||
Ok(via_type) | ||
} | ||
} | ||
|
||
impl ToTokens for ViaType { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
match self { | ||
ViaType::GString { gstring_ident } => gstring_ident.to_tokens(tokens), | ||
ViaType::Int { int_ident } => int_ident.to_tokens(tokens), | ||
} | ||
} | ||
} |
This file contains 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,117 @@ | ||
/* | ||
* Copyright (c) godot-rust; Bromeon and contributors. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
use proc_macro2::{Ident, TokenStream}; | ||
use quote::ToTokens; | ||
use venial::Declaration; | ||
|
||
use crate::util::bail; | ||
use crate::ParseResult; | ||
|
||
use super::c_style_enum::CStyleEnum; | ||
use super::godot_attribute::{GodotAttribute, ViaType}; | ||
use super::newtype::NewtypeStruct; | ||
|
||
/// Stores all relevant data to derive `GodotConvert` and other related traits. | ||
pub struct GodotConvert { | ||
/// The name of the type we're deriving for. | ||
pub ty_name: Ident, | ||
/// The data from the type and `godot` attribute. | ||
pub convert_type: ConvertType, | ||
} | ||
|
||
impl GodotConvert { | ||
pub fn parse_declaration(declaration: Declaration) -> ParseResult<Self> { | ||
let (name, where_clause, generic_params) = match &declaration { | ||
venial::Declaration::Struct(struct_) => ( | ||
struct_.name.clone(), | ||
&struct_.where_clause, | ||
&struct_.generic_params, | ||
), | ||
venial::Declaration::Enum(enum_) => ( | ||
enum_.name.clone(), | ||
&enum_.where_clause, | ||
&enum_.generic_params, | ||
), | ||
other => { | ||
return bail!( | ||
other, | ||
"`GodotConvert` only supports structs and enums currently" | ||
) | ||
} | ||
}; | ||
|
||
if let Some(where_clause) = where_clause { | ||
return bail!( | ||
where_clause, | ||
"`GodotConvert` does not support where clauses" | ||
); | ||
} | ||
|
||
if let Some(generic_params) = generic_params { | ||
return bail!(generic_params, "`GodotConvert` does not support generics"); | ||
} | ||
|
||
let data = ConvertType::parse_declaration(declaration)?; | ||
|
||
Ok(Self { | ||
ty_name: name, | ||
convert_type: data, | ||
}) | ||
} | ||
} | ||
|
||
/// Stores what kind of `GodotConvert` derive we're doing. | ||
pub enum ConvertType { | ||
/// Deriving for a newtype struct. | ||
NewType { field: NewtypeStruct }, | ||
/// Deriving for an enum. | ||
Enum { variants: CStyleEnum, via: ViaType }, | ||
} | ||
|
||
impl ConvertType { | ||
pub fn parse_declaration(declaration: Declaration) -> ParseResult<Self> { | ||
let attribute = GodotAttribute::parse_attribute(&declaration)?; | ||
|
||
match &declaration { | ||
Declaration::Struct(struct_) => { | ||
let GodotAttribute::Transparent { .. } = attribute else { | ||
return bail!(attribute.span(), "`GodotConvert` on structs only works with `#[godot(transparent)]` currently"); | ||
}; | ||
|
||
Ok(Self::NewType { | ||
field: NewtypeStruct::parse_struct(struct_)?, | ||
}) | ||
} | ||
Declaration::Enum(enum_) => { | ||
let GodotAttribute::Via { via_type, .. } = attribute else { | ||
return bail!( | ||
attribute.span(), | ||
"`GodotConvert` on enums requires `#[godot(via = ...)]` currently" | ||
); | ||
}; | ||
|
||
Ok(Self::Enum { | ||
variants: CStyleEnum::parse_enum(enum_)?, | ||
via: via_type, | ||
}) | ||
} | ||
_ => bail!( | ||
declaration, | ||
"`GodotConvert` only supports structs and enums currently" | ||
), | ||
} | ||
} | ||
|
||
/// Returns the type for use in `type Via = <type>;` in `GodotConvert` implementations. | ||
pub fn via_type(&self) -> TokenStream { | ||
match self { | ||
ConvertType::NewType { field } => field.ty.to_token_stream(), | ||
ConvertType::Enum { via, .. } => via.to_token_stream(), | ||
} | ||
} | ||
} |
This file contains 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,16 @@ | ||
/* | ||
* Copyright (c) godot-rust; Bromeon and contributors. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
mod c_style_enum; | ||
mod godot_attribute; | ||
mod godot_convert; | ||
mod newtype; | ||
|
||
pub use c_style_enum::*; | ||
pub use godot_attribute::*; | ||
pub use godot_convert::*; | ||
pub use newtype::*; |
Oops, something went wrong.