Skip to content

Commit 535ec61

Browse files
authored
Merge pull request #599 from lilizoey/refactor/rewrite-convert-derives
Rewrite the derive macros related to `GodotConvert`
2 parents c96f4f6 + 4b445c6 commit 535ec61

20 files changed

+888
-1180
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use proc_macro2::{Ident, Literal, Span, TokenTree};
9+
10+
use crate::util::{bail, error};
11+
use crate::ParseResult;
12+
13+
/// Stores info from c-style enums for use in deriving `GodotConvert` and other related traits.
14+
#[derive(Clone, Debug)]
15+
pub struct CStyleEnum {
16+
/// The names of each variant.
17+
enumerator_names: Vec<Ident>,
18+
/// The discriminants of each variant, both explicit and implicit.
19+
enumerator_ords: Vec<Literal>,
20+
}
21+
22+
impl CStyleEnum {
23+
/// Parses the enum.
24+
///
25+
/// Ensures all the variants are unit variants, and that any explicit discriminants are integer literals.
26+
pub fn parse_enum(enum_: &venial::Enum) -> ParseResult<Self> {
27+
let variants = enum_
28+
.variants
29+
.items()
30+
.map(CStyleEnumerator::parse_enum_variant)
31+
.collect::<ParseResult<Vec<_>>>()?;
32+
33+
let (names, discriminants) = Self::create_discriminant_mapping(variants)?;
34+
35+
Ok(Self {
36+
enumerator_names: names,
37+
enumerator_ords: discriminants,
38+
})
39+
}
40+
41+
fn create_discriminant_mapping(
42+
enumerators: Vec<CStyleEnumerator>,
43+
) -> ParseResult<(Vec<Ident>, Vec<Literal>)> {
44+
// See here for how implicit discriminants are decided
45+
// https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants
46+
let mut names = Vec::new();
47+
let mut discriminants = Vec::new();
48+
49+
let mut last_discriminant = None;
50+
for enumerator in enumerators.into_iter() {
51+
let discriminant_span = enumerator.discriminant_span();
52+
53+
let discriminant = match enumerator.discriminant_as_i64()? {
54+
Some(discriminant) => discriminant,
55+
None => last_discriminant.unwrap_or(0) + 1,
56+
};
57+
last_discriminant = Some(discriminant);
58+
59+
let mut discriminant = Literal::i64_unsuffixed(discriminant);
60+
discriminant.set_span(discriminant_span);
61+
62+
names.push(enumerator.name);
63+
discriminants.push(discriminant)
64+
}
65+
66+
Ok((names, discriminants))
67+
}
68+
69+
/// Returns the names of the variants, in order of the variants.
70+
pub fn names(&self) -> &[Ident] {
71+
&self.enumerator_names
72+
}
73+
74+
/// Returns the discriminants of each variant, in order of the variants.
75+
pub fn discriminants(&self) -> &[Literal] {
76+
&self.enumerator_ords
77+
}
78+
79+
/// Return a hint string for use with `PropertyHint::ENUM` where each variant has an explicit integer hint.
80+
pub fn to_int_hint(&self) -> String {
81+
self.enumerator_names
82+
.iter()
83+
.zip(self.enumerator_ords.iter())
84+
.map(|(name, discrim)| format!("{name}:{discrim}"))
85+
.collect::<Vec<_>>()
86+
.join(",")
87+
}
88+
89+
/// Return a hint string for use with `PropertyHint::ENUM` where the variants are just kept as strings.
90+
pub fn to_string_hint(&self) -> String {
91+
self.enumerator_names
92+
.iter()
93+
.map(ToString::to_string)
94+
.collect::<Vec<_>>()
95+
.join(",")
96+
}
97+
}
98+
99+
/// Each variant in a c-style enum.
100+
#[derive(Clone, Debug)]
101+
pub struct CStyleEnumerator {
102+
/// The name of the variant.
103+
name: Ident,
104+
/// The explicit discriminant of the variant, `None` means there was no explicit discriminant.
105+
discriminant: Option<TokenTree>,
106+
}
107+
108+
impl CStyleEnumerator {
109+
/// Parse an enum variant, erroring if it isn't a unit variant.
110+
fn parse_enum_variant(enum_variant: &venial::EnumVariant) -> ParseResult<Self> {
111+
match enum_variant.contents {
112+
venial::StructFields::Unit => {}
113+
_ => {
114+
return bail!(
115+
&enum_variant.contents,
116+
"GodotConvert only supports c-style enums"
117+
)
118+
}
119+
}
120+
121+
Ok(Self {
122+
name: enum_variant.name.clone(),
123+
discriminant: enum_variant.value.as_ref().map(|val| &val.value).cloned(),
124+
})
125+
}
126+
127+
/// Returns the discriminant parsed as an i64 literal.
128+
fn discriminant_as_i64(&self) -> ParseResult<Option<i64>> {
129+
let Some(discriminant) = self.discriminant.as_ref() else {
130+
return Ok(None);
131+
};
132+
133+
let int = discriminant
134+
.to_string()
135+
.parse::<i64>()
136+
.map_err(|_| error!(discriminant, "expected i64 literal"))?;
137+
138+
Ok(Some(int))
139+
}
140+
141+
/// Returns a span suitable for the discriminant of the variant.
142+
///
143+
/// If there was no explicit discriminant, this will use the span of the name instead.
144+
fn discriminant_span(&self) -> Span {
145+
match &self.discriminant {
146+
Some(discriminant) => discriminant.span(),
147+
None => self.name.span(),
148+
}
149+
}
150+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use proc_macro2::{Ident, Span, TokenStream};
9+
use quote::ToTokens;
10+
use venial::Declaration;
11+
12+
use crate::util::{bail, KvParser};
13+
use crate::ParseResult;
14+
15+
/// Stores data related to the `#[godot(..)]` attribute.
16+
pub enum GodotAttribute {
17+
/// `#[godot(transparent)]`
18+
Transparent { span: Span },
19+
/// `#[godot(via = via_type)]`
20+
Via { span: Span, via_type: ViaType },
21+
}
22+
23+
impl GodotAttribute {
24+
pub fn parse_attribute(declaration: &Declaration) -> ParseResult<Self> {
25+
let mut parser = KvParser::parse_required(declaration.attributes(), "godot", declaration)?;
26+
let attribute = Self::parse(&mut parser)?;
27+
parser.finish()?;
28+
29+
Ok(attribute)
30+
}
31+
32+
fn parse(parser: &mut KvParser) -> ParseResult<Self> {
33+
let span = parser.span();
34+
35+
if parser.handle_alone("transparent")? {
36+
return Ok(Self::Transparent { span });
37+
}
38+
39+
if let Some(via_type) = parser.handle_ident("via")? {
40+
return Ok(Self::Via {
41+
span,
42+
via_type: ViaType::parse_ident(via_type)?,
43+
});
44+
}
45+
46+
bail!(
47+
span,
48+
"expected either `#[godot(transparent)]` or `#[godot(via = <via_type>)]`"
49+
)
50+
}
51+
52+
/// The span of the entire attribute.
53+
///
54+
/// Specifically this is the span of the `[ ]` group from a `#[godot(...)]` attribute.
55+
pub fn span(&self) -> Span {
56+
match self {
57+
GodotAttribute::Transparent { span } => *span,
58+
GodotAttribute::Via { span, .. } => *span,
59+
}
60+
}
61+
}
62+
63+
/// The via type from a `#[godot(via = via_type)]` attribute.
64+
pub enum ViaType {
65+
/// The via type is `GString`
66+
GString { gstring_ident: Ident },
67+
/// The via type is an integer
68+
Int { int_ident: Ident },
69+
}
70+
71+
impl ViaType {
72+
fn parse_ident(ident: Ident) -> ParseResult<Self> {
73+
let via_type = match ident.to_string().as_str() {
74+
"GString" => ViaType::GString { gstring_ident: ident },
75+
"i8" |"i16" | "i32" | "i64" | "u8" | "u16" | "u32" => ViaType::Int { int_ident: ident },
76+
other => return bail!(ident, "Via type `{other}` is not supported, expected one of: GString, i8, i16, i32, i64, u8, u16, u32")
77+
};
78+
79+
Ok(via_type)
80+
}
81+
}
82+
83+
impl ToTokens for ViaType {
84+
fn to_tokens(&self, tokens: &mut TokenStream) {
85+
match self {
86+
ViaType::GString { gstring_ident } => gstring_ident.to_tokens(tokens),
87+
ViaType::Int { int_ident } => int_ident.to_tokens(tokens),
88+
}
89+
}
90+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use proc_macro2::{Ident, TokenStream};
9+
use quote::ToTokens;
10+
use venial::Declaration;
11+
12+
use crate::util::bail;
13+
use crate::ParseResult;
14+
15+
use super::c_style_enum::CStyleEnum;
16+
use super::godot_attribute::{GodotAttribute, ViaType};
17+
use super::newtype::NewtypeStruct;
18+
19+
/// Stores all relevant data to derive `GodotConvert` and other related traits.
20+
pub struct GodotConvert {
21+
/// The name of the type we're deriving for.
22+
pub ty_name: Ident,
23+
/// The data from the type and `godot` attribute.
24+
pub convert_type: ConvertType,
25+
}
26+
27+
impl GodotConvert {
28+
pub fn parse_declaration(declaration: Declaration) -> ParseResult<Self> {
29+
let (name, where_clause, generic_params) = match &declaration {
30+
venial::Declaration::Struct(struct_) => (
31+
struct_.name.clone(),
32+
&struct_.where_clause,
33+
&struct_.generic_params,
34+
),
35+
venial::Declaration::Enum(enum_) => (
36+
enum_.name.clone(),
37+
&enum_.where_clause,
38+
&enum_.generic_params,
39+
),
40+
other => {
41+
return bail!(
42+
other,
43+
"`GodotConvert` only supports structs and enums currently"
44+
)
45+
}
46+
};
47+
48+
if let Some(where_clause) = where_clause {
49+
return bail!(
50+
where_clause,
51+
"`GodotConvert` does not support where clauses"
52+
);
53+
}
54+
55+
if let Some(generic_params) = generic_params {
56+
return bail!(generic_params, "`GodotConvert` does not support generics");
57+
}
58+
59+
let data = ConvertType::parse_declaration(declaration)?;
60+
61+
Ok(Self {
62+
ty_name: name,
63+
convert_type: data,
64+
})
65+
}
66+
}
67+
68+
/// Stores what kind of `GodotConvert` derive we're doing.
69+
pub enum ConvertType {
70+
/// Deriving for a newtype struct.
71+
NewType { field: NewtypeStruct },
72+
/// Deriving for an enum.
73+
Enum { variants: CStyleEnum, via: ViaType },
74+
}
75+
76+
impl ConvertType {
77+
pub fn parse_declaration(declaration: Declaration) -> ParseResult<Self> {
78+
let attribute = GodotAttribute::parse_attribute(&declaration)?;
79+
80+
match &declaration {
81+
Declaration::Struct(struct_) => {
82+
let GodotAttribute::Transparent { .. } = attribute else {
83+
return bail!(attribute.span(), "`GodotConvert` on structs only works with `#[godot(transparent)]` currently");
84+
};
85+
86+
Ok(Self::NewType {
87+
field: NewtypeStruct::parse_struct(struct_)?,
88+
})
89+
}
90+
Declaration::Enum(enum_) => {
91+
let GodotAttribute::Via { via_type, .. } = attribute else {
92+
return bail!(
93+
attribute.span(),
94+
"`GodotConvert` on enums requires `#[godot(via = ...)]` currently"
95+
);
96+
};
97+
98+
Ok(Self::Enum {
99+
variants: CStyleEnum::parse_enum(enum_)?,
100+
via: via_type,
101+
})
102+
}
103+
_ => bail!(
104+
declaration,
105+
"`GodotConvert` only supports structs and enums currently"
106+
),
107+
}
108+
}
109+
110+
/// Returns the type for use in `type Via = <type>;` in `GodotConvert` implementations.
111+
pub fn via_type(&self) -> TokenStream {
112+
match self {
113+
ConvertType::NewType { field } => field.ty.to_token_stream(),
114+
ConvertType::Enum { via, .. } => via.to_token_stream(),
115+
}
116+
}
117+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
mod c_style_enum;
9+
mod godot_attribute;
10+
mod godot_convert;
11+
mod newtype;
12+
13+
pub use c_style_enum::*;
14+
pub use godot_attribute::*;
15+
pub use godot_convert::*;
16+
pub use newtype::*;

0 commit comments

Comments
 (0)