Skip to content

Commit

Permalink
FooBar
Browse files Browse the repository at this point in the history
  • Loading branch information
lilizoey committed Feb 7, 2024
1 parent a0e56db commit 5a90282
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 30 deletions.
17 changes: 11 additions & 6 deletions godot-macros/src/derive/data_models/godot_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use crate::ParseResult;

/// Stores data related to the `#[godot(..)]` attribute.
pub enum GodotAttribute {
/// `#[godot(transparent)]`
Transparent { span: Span },
/// `#[godot(via = via_type)]`
Via { via_type: ViaType },
}

Expand All @@ -40,16 +42,19 @@ impl GodotAttribute {
}
}

/// The via type from a `#[godot(via = via_type)]` attribute.
pub enum ViaType {
GString(Ident),
Int(Ident),
/// 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(ident),
"i8" |"i16" | "i32" | "i64" | "u8" | "u16" | "u32" => ViaType::Int(ident),
"GString" => ViaType::GString { gstring_ident: ident },
"i8" |"i16" | "i32" | "i64" | "u8" | "u16" | "u32" => ViaType::Int { int_ident: ident },
other => return bail!(ident, "Via type `{}` is not supported, expected one of: GString, i8, i16, i32, i64, u8, u16, u32", other)
};

Expand All @@ -60,8 +65,8 @@ impl ViaType {
impl ToTokens for ViaType {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ViaType::GString(ident) => ident.to_tokens(tokens),
ViaType::Int(ident) => ident.to_tokens(tokens),
ViaType::GString { gstring_ident } => gstring_ident.to_tokens(tokens),
ViaType::Int { int_ident } => int_ident.to_tokens(tokens),
}
}
}
19 changes: 12 additions & 7 deletions godot-macros/src/derive/data_models/godot_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ use super::godot_attribute::{GodotAttribute, ViaType};
use super::newtype::NewtypeField;
use super::unit_only_enum::UnitOnlyEnum;

/// Stores all relevant data to derive `GodotConvert` and other related traits.
pub struct GodotConvert {
/// The name of the type we're deriving for.
pub name: Ident,
/// The data from the type and `godot` attribute.
pub data: ConvertData,
}

Expand Down Expand Up @@ -53,27 +56,28 @@ impl GodotConvert {
return bail!(generic_params, "`GodotConvert` does not support generics");
}

let data = ConvertData::parse_declaration(&declaration)?;
let data = ConvertData::parse_declaration(declaration)?;

Ok(Self { name, data })
}
}

/// Stores what kind of `GodotConvert` derive we're doing.
pub enum ConvertData {
NewType {
field: NewtypeField,
},
/// Deriving for a newtype struct.
NewType { field: NewtypeField },
/// Deriving for an enum.
Enum {
variants: UnitOnlyEnum,
via: ViaType,
},
}

impl ConvertData {
pub fn parse_declaration(declaration: &Declaration) -> ParseResult<Self> {
let attribute = GodotAttribute::parse_attribute(declaration)?;
pub fn parse_declaration(declaration: Declaration) -> ParseResult<Self> {
let attribute = GodotAttribute::parse_attribute(&declaration)?;

match declaration {
match &declaration {
Declaration::Struct(struct_) => {
if let GodotAttribute::Via { via_type: ty } = attribute {
return bail!(ty, "`GodotConvert` on structs only works with `#[godot(transparent)]` currently");
Expand Down Expand Up @@ -106,6 +110,7 @@ impl ConvertData {
}
}

/// Returns the type for use in `type Via = <type>;` in `GodotConvert` implementations.
pub fn via_type(&self) -> TokenStream {
match self {
ConvertData::NewType { field } => field.ty.to_token_stream(),
Expand Down
20 changes: 19 additions & 1 deletion godot-macros/src/derive/data_models/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ use venial::TyExpr;
use crate::util::bail;
use crate::ParseResult;

/// Stores info from the field of a newtype for use in deriving `GodotConvert` and other related traits.
pub struct NewtypeField {
// If none, then it's the first field of a tuple-struct.
/// The name of the newtype field.
///
/// If `None`, then this is represents a tuple-struct with one field.
pub name: Option<Ident>,
/// The type of the newtype field.
pub ty: TyExpr,
}

impl NewtypeField {
/// Parses a struct into a newtype field.
///
/// This will fail if the struct doesn't have exactly one field.
pub fn parse_struct(struct_: &venial::Struct) -> ParseResult<NewtypeField> {
match &struct_.fields {
venial::StructFields::Unit => bail!(&struct_.fields, "GodotConvert expects a struct with a single field, unit structs are currently not supported"),
Expand All @@ -43,6 +50,17 @@ impl NewtypeField {
}
}

/// 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`.
/// For instance:
/// ```
/// struct Foo(i64);
///
/// let mut foo = Foo { 0: 10 };
/// foo.0 = 20;
/// println!("{}", foo.0);
/// ```
pub fn field_name(&self) -> TokenStream {
match &self.name {
Some(name) => quote! { #name },
Expand Down
34 changes: 27 additions & 7 deletions godot-macros/src/derive/data_models/unit_only_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ use proc_macro2::{Ident, Literal, Span, TokenTree};
use crate::util::{bail, error};
use crate::ParseResult;

/// Stores info from unit-only enums for use in deriving `GodotConvert` and other related traits.
#[derive(Debug, Clone)]
pub struct UnitOnlyEnum {
/// The names of each variant.
names: Vec<Ident>,
/// The discriminants of each variant, both explicit and implicit.
discriminants: Vec<Literal>,
}

impl UnitOnlyEnum {
/// Parses the enum.
///
/// Ensures the enum is unit-only, and that any explicit discriminants are integer literals.
pub fn parse_enum(enum_: &venial::Enum) -> ParseResult<Self> {
let variants = enum_
.variants
Expand All @@ -35,6 +41,8 @@ impl UnitOnlyEnum {
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();

Expand All @@ -58,14 +66,17 @@ impl UnitOnlyEnum {
Ok((names, discriminants))
}

pub fn discriminants(&self) -> &[Literal] {
&self.discriminants
}

/// Returns the names of the variants, in order of the variants.
pub fn names(&self) -> &[Ident] {
&self.names
}

/// Returns the discriminants of each variant, in order of the variants.
pub fn discriminants(&self) -> &[Literal] {
&self.discriminants
}

/// 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.names
.iter()
Expand All @@ -75,6 +86,7 @@ impl UnitOnlyEnum {
.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.names
.iter()
Expand All @@ -84,14 +96,18 @@ impl UnitOnlyEnum {
}
}

/// Each unit-variant in a unit-only enum.
#[derive(Debug, Clone)]
pub struct UnitEnumVariant {
pub name: Ident,
pub discriminant: Option<TokenTree>,
/// The name of the variant.
name: Ident,
/// The explicit discriminant of the variant, `None` means there was no explicit discriminant.
discriminant: Option<TokenTree>,
}

impl UnitEnumVariant {
pub fn parse_enum_variant(enum_variant: &venial::EnumVariant) -> ParseResult<Self> {
/// 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 => {}
_ => {
Expand All @@ -108,6 +124,7 @@ impl UnitEnumVariant {
})
}

/// 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);
Expand All @@ -121,6 +138,9 @@ impl UnitEnumVariant {
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(),
Expand Down
3 changes: 3 additions & 0 deletions godot-macros/src/derive/derive_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ use crate::ParseResult;

use super::data_models::GodotConvert;

/// Derives `Export` for the declaration.
///
/// This currently just reuses the property hint from the `Var` implementation.
pub fn derive_export(declaration: Declaration) -> ParseResult<TokenStream> {
let GodotConvert { name, .. } = GodotConvert::parse_declaration(declaration)?;

Expand Down
14 changes: 10 additions & 4 deletions godot-macros/src/derive/derive_from_godot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,29 @@ use crate::ParseResult;

use super::data_models::{NewtypeField, UnitOnlyEnum};

/// Derives `FromGodot` for the given `GodotConvert`.
///
/// There is no dedicated `FromGodot` derive macro currently, this is instead called by the `GodotConvert` derive macro.
pub fn derive_from_godot(convert: &GodotConvert) -> ParseResult<TokenStream> {
let GodotConvert { name, data } = convert;

match data {
ConvertData::NewType { field } => from_newtype(name, field),
ConvertData::Enum {
variants,
via: ViaType::GString(_),
via: ViaType::GString { .. },
} => from_enum_string(name, variants),
ConvertData::Enum {
variants,
via: ViaType::Int(int),
} => from_enum_int(name, variants, int),
via: ViaType::Int { int_ident },
} => from_enum_int(name, variants, int_ident),
}
}

/// Derives `FromGodot` for newtype structs.
fn from_newtype(name: &Ident, field: &NewtypeField) -> ParseResult<TokenStream> {
// For tuple structs this ends up using the alternate tuple-struct constructor syntax of
// TupleStruct { .0: value }
// TupleStruct { 0: value }
let field_name = field.field_name();
let via_type = &field.ty;

Expand All @@ -44,6 +48,7 @@ fn from_newtype(name: &Ident, field: &NewtypeField) -> ParseResult<TokenStream>
})
}

/// Derives `FromGodot` for enums with a via type of integers.
fn from_enum_int(name: &Ident, enum_: &UnitOnlyEnum, int: &Ident) -> ParseResult<TokenStream> {
let discriminants = enum_.discriminants();
let names = enum_.names();
Expand All @@ -63,6 +68,7 @@ fn from_enum_int(name: &Ident, enum_: &UnitOnlyEnum, int: &Ident) -> ParseResult
})
}

/// Derives `FromGodot` for enums with a via type of `GString`.
fn from_enum_string(name: &Ident, enum_: &UnitOnlyEnum) -> ParseResult<TokenStream> {
let names = enum_.names();
let names_str = names.iter().map(ToString::to_string).collect::<Vec<_>>();
Expand Down
3 changes: 3 additions & 0 deletions godot-macros/src/derive/derive_godot_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use crate::derive::data_models::GodotConvert;

use super::{derive_from_godot, derive_to_godot};

/// Derives `GodotConvert` for the given declaration.
///
/// This also derives `FromGodot` and `ToGodot`.
pub fn derive_godot_convert(declaration: Declaration) -> ParseResult<TokenStream> {
let convert = GodotConvert::parse_declaration(declaration)?;

Expand Down
12 changes: 9 additions & 3 deletions godot-macros/src/derive/derive_to_godot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,26 @@ use crate::ParseResult;

use super::data_models::{NewtypeField, UnitOnlyEnum};

/// Derives `ToGodot` for the given `GodotConvert`.
///
/// There is no dedicated `ToGodot` derive macro currently, this is instead called by the `GodotConvert` derive macro.
pub fn derive_to_godot(convert: &GodotConvert) -> ParseResult<TokenStream> {
let GodotConvert { name, data } = convert;

match data {
ConvertData::NewType { field } => to_newtype(name, field),
ConvertData::Enum {
variants,
via: ViaType::GString(_),
via: ViaType::GString { .. },
} => to_enum_string(name, variants),
ConvertData::Enum {
variants,
via: ViaType::Int(int),
} => to_enum_int(name, variants, int),
via: ViaType::Int { int_ident },
} => to_enum_int(name, variants, int_ident),
}
}

/// Derives `ToGodot` for newtype structs.
fn to_newtype(name: &Ident, field: &NewtypeField) -> ParseResult<TokenStream> {
let field_name = field.field_name();
let via_type = &field.ty;
Expand All @@ -46,6 +50,7 @@ fn to_newtype(name: &Ident, field: &NewtypeField) -> ParseResult<TokenStream> {
})
}

/// Derives `ToGodot` for enums with a via type of integers.
fn to_enum_int(name: &Ident, enum_: &UnitOnlyEnum, int: &Ident) -> ParseResult<TokenStream> {
let discriminants = enum_.discriminants();
let names = enum_.names();
Expand All @@ -63,6 +68,7 @@ fn to_enum_int(name: &Ident, enum_: &UnitOnlyEnum, int: &Ident) -> ParseResult<T
})
}

/// Derives `ToGodot` for enums with a via type of `GString`.
fn to_enum_string(name: &Ident, enum_: &UnitOnlyEnum) -> ParseResult<TokenStream> {
let names = enum_.names();
let names_str = names.iter().map(ToString::to_string).collect::<Vec<_>>();
Expand Down
10 changes: 8 additions & 2 deletions godot-macros/src/derive/derive_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ use crate::ParseResult;

use super::data_models::GodotConvert;

/// Derives `Var` for the given declaration.
///
/// This uses `ToGodot` and `FromGodot` for the `get_property` and `set_property` implementations.
pub fn derive_var(declaration: Declaration) -> ParseResult<TokenStream> {
let convert = GodotConvert::parse_declaration(declaration)?;

Expand All @@ -38,6 +41,9 @@ pub fn derive_var(declaration: Declaration) -> ParseResult<TokenStream> {
})
}

/// Make an appropriate property hint implementation.
///
/// For newtype structs we just defer to the wrapped type. For enums we use `PropertyHint::ENUM` with an appropriate hint string.
fn create_property_hint_impl(convert: &GodotConvert) -> TokenStream {
use super::data_models::ConvertData as Data;
use super::data_models::ViaType;
Expand All @@ -51,8 +57,8 @@ fn create_property_hint_impl(convert: &GodotConvert) -> TokenStream {
}
Data::Enum { variants, via } => {
let hint_string = match via {
ViaType::GString(_) => variants.to_string_hint(),
ViaType::Int(_) => variants.to_int_hint(),
ViaType::GString { .. } => variants.to_string_hint(),
ViaType::Int { .. } => variants.to_int_hint(),
};

quote! {
Expand Down

0 comments on commit 5a90282

Please sign in to comment.