godot-macros 0.5.5

Internal crate used by godot-rust
/*
 * 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::{GenericParamList, WhereClause};

use super::c_style_enum::CStyleEnum;
use super::godot_attribute::{GodotAttribute, ViaType};
use super::newtype::NewtypeStruct;
use crate::ParseResult;
use crate::util::bail;

/// 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,
    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 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,
                    "#[derive(GodotConvert)] only supports structs and enums"
                );
            }
        };

        Ok(Self {
            ty_name: name,
            where_clause,
            generic_params,
            convert_type: data,
        })
    }

    /// Generic parameters as arguments: `impl Type<GenericArgs>`.
    pub fn generic_args(&self) -> Option<venial::InlineGenericArgs<'_>> {
        self.generic_params
            .as_ref()
            .map(|params| params.as_inline_args())
    }

    /// Errors if the type is generic, for derives that don't support it yet.
    pub fn ensure_no_generics(&self, derive_name: &str) -> ParseResult<()> {
        if let Some(generic_params) = &self.generic_params {
            return bail!(
                generic_params,
                "#[derive({derive_name})] does not support lifetimes or generic parameters"
            );
        }

        if let Some(where_clause) = &self.where_clause {
            return bail!(
                where_clause,
                "#[derive({derive_name})] does not support where clauses"
            );
        }

        Ok(())
    }
}

/// 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(item: &venial::Item) -> ParseResult<Self> {
        let attribute = GodotAttribute::parse_attribute(item)?;

        match item {
            venial::Item::Struct(struct_) => {
                let GodotAttribute::Transparent { .. } = attribute else {
                    return bail!(
                        attribute.span(),
                        "#[derive(GodotConvert)] on structs currently only works with #[godot(transparent)]"
                    );
                };

                Ok(Self::NewType {
                    field: NewtypeStruct::parse_struct(struct_)?,
                })
            }
            venial::Item::Enum(enum_) => {
                let GodotAttribute::Via { via_type, .. } = attribute else {
                    return bail!(
                        attribute.span(),
                        "#[derive(GodotConvert)] on enums requires #[godot(via = ...)]"
                    );
                };

                Ok(Self::Enum {
                    variants: CStyleEnum::parse_enum(enum_)?,
                    via: via_type,
                })
            }
            _ => unreachable!(), // already checked outside.
        }
    }

    /// Returns the type for use in `type Via = <type>;` in `GodotConvert` implementations.
    pub fn via_type(&self) -> TokenStream {
        match self {
            ConvertType::NewType { field } => field.sized.ty.to_token_stream(),
            ConvertType::Enum { via, .. } => via.to_token_stream(),
        }
    }
}