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

Rewrite the derive macros related to GodotConvert #599

Merged
merged 2 commits into from
Feb 11, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 150 additions & 0 deletions godot-macros/src/derive/data_models/c_style_enum.rs
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(),
}
}
}
90 changes: 90 additions & 0 deletions godot-macros/src/derive/data_models/godot_attribute.rs
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),
}
}
}
117 changes: 117 additions & 0 deletions godot-macros/src/derive/data_models/godot_convert.rs
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(),
}
}
}
16 changes: 16 additions & 0 deletions godot-macros/src/derive/data_models/mod.rs
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::*;